/*-------------------------------------------------------------------------
 *
 * datetime.c
 *	  日期/时间类型的支持函数。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/datetime.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <ctype.h>
#include <limits.h>
#include <math.h>

#include "access/htup_details.h"
#include "access/xact.h"
#include "catalog/pg_type.h"
#include "common/int.h"
#include "common/string.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "utils/builtins.h"
#include "utils/date.h"
#include "utils/datetime.h"
#include "utils/memutils.h"
#include "utils/tzparser.h"

static int	fc_DecodeNumber(int fc_flen, char *fc_field, bool fc_haveTextMonth,
						 int fc_fmask, int *fc_tmask,
						 struct pg_tm *fc_tm, fsec_t *fc_fsec, bool *fc_is2digits);
static int	fc_DecodeNumberField(int fc_len, char *fc_str,
							  int fc_fmask, int *fc_tmask,
							  struct pg_tm *fc_tm, fsec_t *fc_fsec, bool *fc_is2digits);
static int	fc_DecodeTimeCommon(char *fc_str, int fc_fmask, int fc_range,
							 int *fc_tmask, struct pg_itm *fc_itm);
static int	fc_DecodeTime(char *fc_str, int fc_fmask, int fc_range,
					   int *fc_tmask, struct pg_tm *fc_tm, fsec_t *fc_fsec);
static int	fc_DecodeTimeForInterval(char *fc_str, int fc_fmask, int fc_range,
								  int *fc_tmask, struct pg_itm_in *fc_itm_in);
static const datetkn *fc_datebsearch(const char *fc_key, const datetkn *fc_base, int fc_nel);
static int	fc_DecodeDate(char *fc_str, int fc_fmask, int *fc_tmask, bool *fc_is2digits,
					   struct pg_tm *fc_tm);
static char *fc_AppendSeconds(char *fc_cp, int fc_sec, fsec_t fc_fsec,
						   int fc_precision, bool fc_fillzeros);
static bool fc_int64_multiply_add(int64 fc_val, int64 fc_multiplier, int64 *fc_sum);
static bool fc_AdjustFractMicroseconds(double fc_frac, int64 fc_scale,
									struct pg_itm_in *fc_itm_in);
static bool fc_AdjustFractDays(double fc_frac, int fc_scale,
							struct pg_itm_in *fc_itm_in);
static bool fc_AdjustFractYears(double fc_frac, int fc_scale,
							 struct pg_itm_in *fc_itm_in);
static bool fc_AdjustMicroseconds(int64 fc_val, double fc_fval, int64 fc_scale,
							   struct pg_itm_in *fc_itm_in);
static bool fc_AdjustDays(int64 fc_val, int fc_scale,
					   struct pg_itm_in *fc_itm_in);
static bool fc_AdjustMonths(int64 fc_val, struct pg_itm_in *fc_itm_in);
static bool fc_AdjustYears(int64 fc_val, int fc_scale,
						struct pg_itm_in *fc_itm_in);
static int	fc_DetermineTimeZoneOffsetInternal(struct pg_tm *fc_tm, pg_tz *fc_tzp,
											pg_time_t *fc_tp);
static bool fc_DetermineTimeZoneAbbrevOffsetInternal(pg_time_t fc_t,
												  const char *fc_abbr, pg_tz *fc_tzp,
												  int *fc_offset, int *fc_isdst);
static pg_tz *fc_FetchDynamicTimeZone(TimeZoneAbbrevTable *fc_tbl, const datetkn *fc_tp);


const int	day_tab[2][13] =
{
	{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0},
	{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0}
};

const char *const months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec", NULL};

const char *const days[] = {"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday", NULL};


/*****************************************************************************
 *	 私有例程														 *
 *****************************************************************************/

/*
 * datetktbl保存日期/时间关键字。
 *
 * 请注意，此表必须严格按字母顺序排列，以允许使用O(ln(N))搜索算法。
 *
 * token字段必须以NUL结尾；我们将条目截断为TOKMAXLEN个字符以适应。
 *
 * 静态表中没有TZ、DTZ或DYNTZ条目；相反，它们是从配置文件加载并存储在zoneabbrevtbl中，其abbrevs[]字段的格式与静态datetktbl相同。
 */
static const datetkn datetktbl[] = {
	/* token, type, value */
	{EARLY, RESERV, DTK_EARLY}, /* "-infinity" 预留用于 "早期时间" */  
	{DA_D, ADBC, AD},			/* "ad" 表示年份 > 0 */
	{"allballs", RESERV, DTK_ZULU}, /* 00:00:00 */
	{"am", AMPM, AM},
	{"apr", MONTH, 4},
	{"april", MONTH, 4},
	{"at", IGNORE_DTF, 0},		/* "at"（丢弃） */  
	{"aug", MONTH, 8},
	{"august", MONTH, 8},
	{DB_C, ADBC, BC},			/* "bc" 表示年份 <= 0 */
	{"d", UNITS, DTK_DAY},		/* "月份的天" 用于 ISO 输入 */  
	{"dec", MONTH, 12},
	{"december", MONTH, 12},
	{"dow", UNITS, DTK_DOW},	/* 周中的天 */  
	{"doy", UNITS, DTK_DOY},	/* 年中的天 */  
	{"dst", DTZMOD, SECS_PER_HOUR},
	{EPOCH, RESERV, DTK_EPOCH}, /* "纪元" 预留用于系统纪元时间 */  
	{"feb", MONTH, 2},
	{"february", MONTH, 2},
	{"fri", DOW, 5},
	{"friday", DOW, 5},
	{"h", UNITS, DTK_HOUR},		/* "小时" */
	{LATE, RESERV, DTK_LATE},	/* 为“晚时间”保留的“无穷大” */
	{"isodow", UNITS, DTK_ISODOW},	/* ISO周几，星期天 == 7 */
	{"isoyear", UNITS, DTK_ISOYEAR},	/* 按照 ISO 周日期计算的年份 */
	{"j", UNITS, DTK_JULIAN},
	{"jan", MONTH, 1},
	{"january", MONTH, 1},
	{"jd", UNITS, DTK_JULIAN},
	{"jul", MONTH, 7},
	{"julian", UNITS, DTK_JULIAN},
	{"july", MONTH, 7},
	{"jun", MONTH, 6},
	{"june", MONTH, 6},
	{"m", UNITS, DTK_MONTH},	/* 用于ISO输入的“月份” */
	{"mar", MONTH, 3},
	{"march", MONTH, 3},
	{"may", MONTH, 5},
	{"mm", UNITS, DTK_MINUTE},	/* 用于ISO输入的“分钟” */
	{"mon", DOW, 1},
	{"monday", DOW, 1},
	{"nov", MONTH, 11},
	{"november", MONTH, 11},
	{NOW, RESERV, DTK_NOW},		/* 当前交易时间 */
	{"oct", MONTH, 10},
	{"october", MONTH, 10},
	{"on", IGNORE_DTF, 0},		/* “开” （抛弃） */
	{"pm", AMPM, PM},
	{"s", UNITS, DTK_SECOND},	/* "秒" 用于 ISO 输入 */
	{"sat", DOW, 6},
	{"saturday", DOW, 6},
	{"sep", MONTH, 9},
	{"sept", MONTH, 9},
	{"september", MONTH, 9},
	{"sun", DOW, 0},
	{"sunday", DOW, 0},
	{"t", ISOTIME, DTK_TIME},	/* ISO 时间字段的填充 */
	{"thu", DOW, 4},
	{"thur", DOW, 4},
	{"thurs", DOW, 4},
	{"thursday", DOW, 4},
	{TODAY, RESERV, DTK_TODAY}, /* 午夜 */
	{TOMORROW, RESERV, DTK_TOMORROW},	/* 明天午夜 */
	{"tue", DOW, 2},
	{"tues", DOW, 2},
	{"tuesday", DOW, 2},
	{"wed", DOW, 3},
	{"wednesday", DOW, 3},
	{"weds", DOW, 3},
	{"y", UNITS, DTK_YEAR},		/* "年" 用于 ISO 输入 */
	{YESTERDAY, RESERV, DTK_YESTERDAY}	/* 昨天午夜 */
};

static const int szdatetktbl = sizeof datetktbl / sizeof datetktbl[0];

/*
 * deltatktbl: 与 datetktbl 相同的格式，但保存用于表示
 * 时间单位的关键字（例如，用于间隔和 EXTRACT）。
 */
static const datetkn deltatktbl[] = {
	/* token, type, value */
	{"@", IGNORE_DTF, 0},		/* postgres 相对前缀 */
	{DAGO, AGO, 0},				/* "之前" 表示负时间偏移 */
	{"c", UNITS, DTK_CENTURY},	/* "世纪" 相对 */
	{"cent", UNITS, DTK_CENTURY},	/* "世纪" 相对 */
	{"centuries", UNITS, DTK_CENTURY},	/* "世纪" 相对 */
	{DCENTURY, UNITS, DTK_CENTURY}, /* "世纪" 相对 */
	{"d", UNITS, DTK_DAY},		/* "天" 相对 */
	{DDAY, UNITS, DTK_DAY},		/* "天" 相对 */
	{"days", UNITS, DTK_DAY},	/* "天" 相对 */
	{"dec", UNITS, DTK_DECADE}, /* "十年" 相对 */
	{DDECADE, UNITS, DTK_DECADE},	/* "十年" 相对 */
	{"decades", UNITS, DTK_DECADE}, /* "十年" 相对 */
	{"decs", UNITS, DTK_DECADE},	/* "十年" 相对 */
	{"h", UNITS, DTK_HOUR},		/* "小时" 相对 */
	{DHOUR, UNITS, DTK_HOUR},	/* "小时" 相对 */
	{"hours", UNITS, DTK_HOUR}, /* "小时" 相对 */
	{"hr", UNITS, DTK_HOUR},	/* "小时" 相对 */
	{"hrs", UNITS, DTK_HOUR},	/* "小时" 相对 */
	{"m", UNITS, DTK_MINUTE},	/* "分钟" 相对 */
	{"microsecon", UNITS, DTK_MICROSEC},	/* "微秒" 相对 */
	{"mil", UNITS, DTK_MILLENNIUM}, /* "千年" 相对 */
	{"millennia", UNITS, DTK_MILLENNIUM},	/* "千年" 相对 */
	{DMILLENNIUM, UNITS, DTK_MILLENNIUM},	/* "千年" 相对 */
	{"millisecon", UNITS, DTK_MILLISEC},	/* 相对 */
	{"mils", UNITS, DTK_MILLENNIUM},	/* "千年" 相对 */
	{"min", UNITS, DTK_MINUTE}, /* "分钟" 相对 */
	{"mins", UNITS, DTK_MINUTE},	/* "分钟" 相对 */
	{DMINUTE, UNITS, DTK_MINUTE},	/* "分钟" 相对 */
	{"minutes", UNITS, DTK_MINUTE}, /* "分钟" 相对 */
	{"mon", UNITS, DTK_MONTH},	/* "月" 相对 */
	{"mons", UNITS, DTK_MONTH}, /* "月" 相对 */
	{DMONTH, UNITS, DTK_MONTH}, /* "月" 相对 */
	{"months", UNITS, DTK_MONTH},
	{"ms", UNITS, DTK_MILLISEC},
	{"msec", UNITS, DTK_MILLISEC},
	{DMILLISEC, UNITS, DTK_MILLISEC},
	{"mseconds", UNITS, DTK_MILLISEC},
	{"msecs", UNITS, DTK_MILLISEC},
	{"qtr", UNITS, DTK_QUARTER},	/* "季度" 相对 */
	{DQUARTER, UNITS, DTK_QUARTER}, /* "季度" 相对 */
	{"s", UNITS, DTK_SECOND},
	{"sec", UNITS, DTK_SECOND},
	{DSECOND, UNITS, DTK_SECOND},
	{"seconds", UNITS, DTK_SECOND},
	{"secs", UNITS, DTK_SECOND},
	{DTIMEZONE, UNITS, DTK_TZ}, /* "时区" 时间偏移 */
	{"timezone_h", UNITS, DTK_TZ_HOUR}, /* 时区小时单位 */
	{"timezone_m", UNITS, DTK_TZ_MINUTE},	/* 时区分钟单位 */
	{"us", UNITS, DTK_MICROSEC},	/* "微秒" 相对 */
	{"usec", UNITS, DTK_MICROSEC},	/* "微秒" 相对 */
	{DMICROSEC, UNITS, DTK_MICROSEC},	/* "微秒" 相对 */
	{"useconds", UNITS, DTK_MICROSEC},	/* "微秒" 相对 */
	{"usecs", UNITS, DTK_MICROSEC}, /* "微秒" 相对 */
	{"w", UNITS, DTK_WEEK},		/* "周" 相对 */
	{DWEEK, UNITS, DTK_WEEK},	/* "周" 相对 */
	{"weeks", UNITS, DTK_WEEK}, /* "weeks" 相对 */
	{"y", UNITS, DTK_YEAR},		/* “年份”相对 */
	{DYEAR, UNITS, DTK_YEAR},	/* “年份”相对 */
	{"years", UNITS, DTK_YEAR}, /* “年”相对 */
	{"yr", UNITS, DTK_YEAR},	/* “年份”相对 */
	{"yrs", UNITS, DTK_YEAR}	/* “年”相对 */
};

static const int szdeltatktbl = sizeof deltatktbl / sizeof deltatktbl[0];

static TimeZoneAbbrevTable *zoneabbrevtbl = NULL;

/* 上述表格中最近查找结果的缓存 */

static const datetkn *datecache[MAXDATEFIELDS] = {NULL};

static const datetkn *deltacache[MAXDATEFIELDS] = {NULL};

static const datetkn *abbrevcache[MAXDATEFIELDS] = {NULL};


/*
 * 日历时间转换为儒略日期。
 * 儒略日期在天文学应用中常用，
 * 因为它在数值上精确且计算简单。
 * 此处的算法将准确地在所有非负的儒略日之间
 * 转换儒略天和日历日期
 * （即从公元前4713年11月24日开始）。
 *
 * 重写以消除溢出问题。这现在允许
 * 例程对从
 * 0到2147483647（公元前4713年11月24日至公元5874898年6月3日）的所有儒略天计数
 * 正常工作，假设使用32位整数。更长的类型也应按其精度的限度正常工作。
 *
 * 实际上，date2j()将在逻辑上产生有效的负儒略日期，
 * 在公元前4713年11月24日之前的合理时间内正常工作。
 * 我们依赖于它在公元前4713年11月1日之前的工作；
 * 请参见IS_VALID_JULIAN()和timestamp.h中的相关评论。
 */

int date2j(int fc_y, int fc_m, int fc_d)
{
	int			fc_julian;
	int			fc_century;

	if (fc_m > 2)
	{
		fc_m += 1;
		fc_y += 4800;
	}
	else
	{
		fc_m += 13;
		fc_y += 4799;
	}

	fc_century = fc_y / 100;
	fc_julian = fc_y * 365 - 32167;
	fc_julian += fc_y / 4 - fc_century + fc_century / 4;
	fc_julian += 7834 * fc_m / 256 + fc_d;

	return fc_julian;
}								/* date2j() */

void j2date(int fc_jd, int *fc_year, int *fc_month, int *fc_day)
{
	unsigned int fc_julian;
	unsigned int fc_quad;
	unsigned int fc_extra;
	int			fc_y;

	fc_julian = fc_jd;
	fc_julian += 32044;
	fc_quad = fc_julian / 146097;
	fc_extra = (fc_julian - fc_quad * 146097) * 4 + 3;
	fc_julian += 60 + fc_quad * 3 + fc_extra / 146097;
	fc_quad = fc_julian / 1461;
	fc_julian -= fc_quad * 1461;
	fc_y = fc_julian * 4 / 1461;
	fc_julian = ((fc_y != 0) ? ((fc_julian + 305) % 365) : ((fc_julian + 306) % 366))
		+ 123;
	fc_y += fc_quad * 4;
	*fc_year = fc_y - 4800;
	fc_quad = fc_julian * 2141 / 65536;
	*fc_day = fc_julian - 7834 * fc_quad / 256;
	*fc_month = (fc_quad + 10) % MONTHS_PER_YEAR + 1;
}								/* j2date() */


/*
 * j2day - 将儒略日期转换为星期几（0..6 == 星期日..星期六）
 *
 * 注意：不同地方使用表述j2day(date - 1)产生
 * 根据惯例0..6 = 星期一..星期天的结果。这有点
 * 不可信，但只要这里的计算只是取模就可以正常工作。
 */
int j2day(int fc_date)
{
	fc_date += 1;
	fc_date %= 7;
	/* 处理如果除法向零截断的情况，因为它可能确实如此 */
	if (fc_date < 0)
		fc_date += 7;

	return fc_date;
}								/* j2day() */


/*
 * 获取当前日期时间（GetCurrentDateTime）
 *
 * 获取事务开始时间（“now()”）并将其分解为 pg_tm 结构，
 * 根据会话时区设置进行转换。
 *
 * 这只是一个用于 GetCurrentTimeUsec 的便利包装器，以涵盖
 * 调用方不需要小数秒或时区偏移的情况。
 */
void GetCurrentDateTime(struct pg_tm *fc_tm)
{
	fsec_t		fc_fsec;

	GetCurrentTimeUsec(fc_tm, &fc_fsec, NULL);
}

/*
 * GetCurrentTimeUsec()
 *
 * 获取交易开始时间（“now()”），分解为 struct pg_tm，
 * 包括小数秒和时区偏移。时间根据会话时区设置进行转换。
 *
 * 调用者可以传递 tzp = NULL 如果他们不需要偏移，但这并不会影响
 * 转换行为（与 timestamp2tm() 不同）。
 *
 * 在内部，我们缓存结果，因为在一个事务中可能会多次调用它，
 * 在此期间 now() 不会改变。
 */
void GetCurrentTimeUsec(struct pg_tm *fc_tm, fsec_t *fc_fsec, int *fc_tzp)
{
	TimestampTz fc_cur_ts = GetCurrentTransactionStartTimestamp();

	/*
	 * 缓存键必须包括当前时间和当前时区。
	 * 通过仅使用指针表示时区，我们假设不同的时区设置
	 * 不可能有相同的指针值。这是通过 pg_tzset() 中使用的哈希表的性质
	 * 成立的；然而，如果我们以后允许哈希中的条目被回收，
	 * 这可能需要重新考虑。
	 */
	static TimestampTz fc_cache_ts = 0;
	static pg_tz *fc_cache_timezone = NULL;
	static struct pg_tm fc_cache_tm;
	static fsec_t fc_cache_fsec;
	static int	fc_cache_tz;

	if (fc_cur_ts != fc_cache_ts || session_timezone != fc_cache_timezone)
	{
		/*
		 * 确保在 timestamp2tm 部分更新后出错时缓存标记为无效。
		 */
		fc_cache_timezone = NULL;

		/*
		 * 执行计算，将结果存储到缓存中。我们并不
		 * 真的期望这里有任何错误，因为当前时间肯定应该在
		 * 范围内，但为了慎重起见还是检查一下。
		 */
		if (timestamp2tm(fc_cur_ts, &fc_cache_tz, &fc_cache_tm, &fc_cache_fsec,
						 NULL, session_timezone) != 0)
			ereport(ERROR,
					(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
					 errmsg("timestamp out of range")));

		/* 好吧，标记缓存为有效。 */
		fc_cache_ts = fc_cur_ts;
		fc_cache_timezone = session_timezone;
	}

	*fc_tm = fc_cache_tm;
	*fc_fsec = fc_cache_fsec;
	if (fc_tzp != NULL)
		*fc_tzp = fc_cache_tz;
}


/*
 * 在 *cp 处附加秒和毫秒（如有）。
 *
 * precision 是小数位数的最大值，fillzeros 表示
 * 补齐到两个整数秒位数字。
 *
 * 返回指向新字符串末尾的指针。不在这里放置 NUL 终止符；
 * 调用者有责任自己 NUL 终止字符串。
 *
 * 请注意，从输入的 sec 和 fsec 值中剥离了任何符号。
 */
static char * fc_AppendSeconds(char *fc_cp, int fc_sec, fsec_t fc_fsec, int fc_precision, bool fc_fillzeros)
{
	Assert(fc_precision >= 0);

	if (fc_fillzeros)
		fc_cp = pg_ultostr_zeropad(fc_cp, Abs(fc_sec), 2);
	else
		fc_cp = pg_ultostr(fc_cp, Abs(fc_sec));

	/* fsec_t 只是一个 int32 */
	if (fc_fsec != 0)
	{
		int32		fc_value = Abs(fc_fsec);
		char	   *fc_end = &fc_cp[fc_precision + 1];
		bool		fc_gotnonzero = false;

		*fc_cp++ = '.';

		/*
		 * 附加毫秒部分。请注意，我们不想在这里有任何
		 * 尾随零，因此由于我们正在反向构建数字，
		 * 将跳过附加零，直到输出一个非零数字。
		 */
		while (fc_precision--)
		{
			int32		fc_oldval = fc_value;
			int32		fc_remainder;

			fc_value /= 10;
			fc_remainder = fc_oldval - fc_value * 10;

			/* 检查我们是否得到了非零值 */
			if (fc_remainder)
				fc_gotnonzero = true;

			if (fc_gotnonzero)
				fc_cp[fc_precision] = '0' + fc_remainder;
			else
				fc_end = &fc_cp[fc_precision];
		}

		/*
		 * 如果我们仍然有非零值，则精度一定不足以打印数字。
		 * 我们将问题推给pg_ultostr（），
		 * 它将在最小有效宽度上生成正确的答案。
		 */
		if (fc_value)
			return pg_ultostr(fc_cp, Abs(fc_fsec));

		return fc_end;
	}
	else
		return fc_cp;
}


/*
 * 上述的变体，专门针对时间戳情况。
 *
 * 返回指向字符串新结尾的指针。 不会放置 NUL 终止符； 调用者负责自己 NUL 终止 str。
 */
static char * fc_AppendTimestampSeconds(char *fc_cp, struct pg_tm *fc_tm, fsec_t fc_fsec)
{
	return fc_AppendSeconds(fc_cp, fc_tm->tm_sec, fc_fsec, MAX_TIMESTAMP_PRECISION, true);
}


/*
 * 将 val * multiplier 添加到 *sum。
 * 如果成功则返回 true，如果溢出则返回 false。
 */
static bool fc_int64_multiply_add(int64 fc_val, int64 fc_multiplier, int64 *fc_sum)
{
	int64		fc_product;

	if (pg_mul_s64_overflow(fc_val, fc_multiplier, &fc_product) ||
		pg_add_s64_overflow(*fc_sum, fc_product, fc_sum))
		return false;
	return true;
}

/*
 * 将 frac 乘以 scale（以产生微秒）并添加到 itm_in->tm_usec。
 * 如果成功则返回 true，如果 itm_in 溢出则返回 false。
 */
static bool fc_AdjustFractMicroseconds(double fc_frac, int64 fc_scale,
						struct pg_itm_in *fc_itm_in)
{
	int64		fc_usec;

	/* 常见情况的快速路径 */
	if (fc_frac == 0)
		return true;

	/*
	 * 我们假设输入的frac的绝对值小于1，因此对于有趣的scale值来说，frac
	 * 或usec的溢出不是问题。
	 */
	fc_frac *= fc_scale;
	fc_usec = (int64) fc_frac;

	/* 将任何小数微秒舍入 */
	fc_frac -= fc_usec;
	if (fc_frac > 0.5)
		fc_usec++;
	else if (fc_frac < -0.5)
		fc_usec--;

	return !pg_add_s64_overflow(fc_itm_in->tm_usec, fc_usec, &fc_itm_in->tm_usec);
}

/*
 * 将frac乘以scale（产生天数）。将结果的整数部分添加到itm_in->tm_mday，将小数部分添加到itm_in->tm_usec。
 * 如果成功则返回true，如果itm_in溢出则返回false。
 */
static bool fc_AdjustFractDays(double fc_frac, int fc_scale,
				struct pg_itm_in *fc_itm_in)
{
	int			fc_extra_days;

	/* 常见情况的快速路径 */
	if (fc_frac == 0)
		return true;

	/*
	 * 我们假设输入 frac 的绝对值小于 1，因此 frac 或 extra_days 的溢出不是问题。
	 */
	fc_frac *= fc_scale;
	fc_extra_days = (int) fc_frac;

	/* ... 但这可能会溢出，如果 tm_mday 已经非零的话 */
	if (pg_add_s32_overflow(fc_itm_in->tm_mday, fc_extra_days, &fc_itm_in->tm_mday))
		return false;

	/* 处理任何小数天 */
	fc_frac -= fc_extra_days;
	return fc_AdjustFractMicroseconds(fc_frac, USECS_PER_DAY, fc_itm_in);
}

/*
 * 将 frac 乘以 scale（以生成年份），然后进一步缩放到月份。
 * 将结果的整数部分加到 itm_in->tm_mon，丢弃任何小数部分。
 * 如果成功则返回 true，如果 itm_in 溢出则返回 false。
 */
static bool fc_AdjustFractYears(double fc_frac, int fc_scale,
				 struct pg_itm_in *fc_itm_in)
{
	/*
	 * 与上述相同，我们假设 abs(frac) < 1，因此这不能因 scale 的任何有趣值而溢出。
	 */
	int			fc_extra_months = (int) rint(fc_frac * fc_scale * MONTHS_PER_YEAR);

	return !pg_add_s32_overflow(fc_itm_in->tm_mon, fc_extra_months, &fc_itm_in->tm_mon);
}

/*
 * 将 (val + fval) * scale 加到 itm_in->tm_usec。
 * 如果成功则返回 true，如果 itm_in 溢出则返回 false。
 */
static bool fc_AdjustMicroseconds(int64 fc_val, double fc_fval, int64 fc_scale,
				   struct pg_itm_in *fc_itm_in)
{
	/* 处理整数部分 */
	if (!fc_int64_multiply_add(fc_val, fc_scale, &fc_itm_in->tm_usec))
		return false;
	/* 处理浮点部分 */
	return fc_AdjustFractMicroseconds(fc_fval, fc_scale, fc_itm_in);
}

/*
 * 将 val 乘以 scale（以生成天数）并加到 itm_in->tm_mday。
 * 如果成功则返回 true，如果 itm_in 溢出则返回 false。
 */
static bool fc_AdjustDays(int64 fc_val, int fc_scale, struct pg_itm_in *fc_itm_in)
{
	int			fc_days;

	if (fc_val < INT_MIN || fc_val > INT_MAX)
		return false;
	return !pg_mul_s32_overflow((int32) fc_val, fc_scale, &fc_days) &&
		!pg_add_s32_overflow(fc_itm_in->tm_mday, fc_days, &fc_itm_in->tm_mday);
}

/*
 * 将 val 添加到 itm_in->tm_mon（这里不需要缩放，因为 val 总是
 * 以月为单位）。
 * 如果成功则返回 true，如果 itm_in 溢出则返回 false。
 */
static bool fc_AdjustMonths(int64 fc_val, struct pg_itm_in *fc_itm_in)
{
	if (fc_val < INT_MIN || fc_val > INT_MAX)
		return false;
	return !pg_add_s32_overflow(fc_itm_in->tm_mon, (int32) fc_val, &fc_itm_in->tm_mon);
}

/*
 * 将 val 乘以缩放（以生成年份）并添加到 itm_in->tm_year。
 * 如果成功则返回 true，如果 itm_in 溢出则返回 false。
 */
static bool fc_AdjustYears(int64 fc_val, int fc_scale,
			struct pg_itm_in *fc_itm_in)
{
	int			fc_years;

	if (fc_val < INT_MIN || fc_val > INT_MAX)
		return false;
	return !pg_mul_s32_overflow((int32) fc_val, fc_scale, &fc_years) &&
		!pg_add_s32_overflow(fc_itm_in->tm_year, fc_years, &fc_itm_in->tm_year);
}


/*
 * 解析数字的小数部分（小数点和可选数字，后跟字符串结束）。将小数值返回到*frac。
 *
 * 如果成功返回0，如果检测到无效输入则返回DTERR代码。
 */
static int fc_ParseFraction(char *fc_cp, double *fc_frac)
{
	/* 调用者应始终传递小数部分的起始部分 */
	Assert(*fc_cp == '.');

	/* 
	 * 我们希望只允许“.”而不带数字，但某些版本的 strtod 
	 * 会对此报告 EINVAL，因此需特殊处理。
	 */
	if (fc_cp[1] == '\0')
	{
		*fc_frac = 0;
	}
	else
	{
		errno = 0;
		*fc_frac = strtod(fc_cp, &fc_cp);
		/* 检查解析失败 */
		if (*fc_cp != '\0' || errno != 0)
			return DTERR_BAD_FORMAT;
	}
	return 0;
}

/*
 * 获取带有适当错误检查的毫秒值。
 * 与ParseFraction相同，除了我们将结果转换为整数微秒。
 */
static int fc_ParseFractionalSecond(char *fc_cp, fsec_t *fc_fsec)
{
	double		fc_frac;
	int			fc_dterr;

	fc_dterr = fc_ParseFraction(fc_cp, &fc_frac);
	if (fc_dterr)
		return fc_dterr;
	*fc_fsec = rint(fc_frac * 1000000);
	return 0;
}


/* ParseDateTime()
 *	根据日期/时间上下文将字符串拆分为标记。
 *	如果成功返回 0，如果检测到无效输入则返回 DTERR 代码。
 *
 * timestr - 输入字符串
 * workbuf - 字段字符串存储的工作空间。这个大小必须
 *	 大于该日期时间类型的最大合法输入 --
 *	 还需一些额外空间以 NUL 终止字段。
 * buflen - workbuf 的大小
 * field[] - 字段字符串的指针在这个数组中返回
 * ftype[] - 字段类型指示符在这个数组中返回
 * maxfields - 上述两个数组的维度
 * *numfields - 设置为检测到的实际字段数量
 *
 * 从输入中提取的字段以单独的、以 NUL 终止的字符串的形式存储在
 * workbuf 的工作空间中。任何文本都将转换为小写。
 *
 * 分配了几种字段类型：
 *	DTK_NUMBER - 数字和（可能）小数点
 *	DTK_DATE - 数字和两个分隔符，或数字和文本
 *	DTK_TIME - 数字、冒号分隔符，和可能的小数点
 *	DTK_STRING - 文本（没有数字或标点符号）
 *	DTK_SPECIAL - 以 "+" 或 "-" 开头，后跟文本
 *	DTK_TZ - 以 "+" 或 "-" 开头，后跟数字（也可包含':'，'.'，'-'）
 *
 * 注意，一些字段类型可以包含意外的项目：
 *	DTK_NUMBER 可以包含日期字段（yy.ddd）
 *	DTK_STRING 可以包含月份（January）和时区（PST）
 *	DTK_DATE 可以包含时区名称（America/New_York，GMT-8）
 */
int ParseDateTime(const char *fc_timestr, char *fc_workbuf, size_t fc_buflen,
			  char **fc_field, int *fc_ftype, int fc_maxfields, int *fc_numfields)
{
	int			fc_nf = 0;
	const char *fc_cp = fc_timestr;
	char	   *fc_bufp = fc_workbuf;
	const char *fc_bufend = fc_workbuf + fc_buflen;

	
/*
	 * 将 "bufptr" 指向的字符设置为 "newchar"，并递增 "bufptr"。
	 * "end" 给出了缓冲区的结束位置 -- 如果没有剩余空间来追加字符到缓冲区，我们将返回错误。
	 * 请注意 "bufptr" 被评估了两次。
	 */
#define APPEND_CHAR(bufptr, end, newchar)		\
	do											\
	{											\
		if (((bufptr) + 1) >= (end))			\
			return DTERR_BAD_FORMAT;			\
		*(bufptr)++ = newchar;					\
	} while (0)

	/***
				 * 已经有日期了吗？那么这可能是一个 POSIX 时间
				 * 时区，带有嵌入的破折号（例如 "PST-3" == "EST"）或
				 * 紧凑时间加上尾随时区（例如 hhmmss-zz）。
				 * - thomas 2001-12-25
				 ***/
	while (*fc_cp != '\0')
	{
		/* 忽略字段之间的空格 */
		if (isspace((unsigned char) *fc_cp))
		{
			fc_cp++;
			continue;
		}

		/* 不接受时区？那么退出... */
		if (fc_nf >= fc_maxfields)
			return DTERR_BAD_FORMAT;
		fc_field[fc_nf] = fc_bufp;

		/* 健康检查；不应该失败此测试 */
		if (isdigit((unsigned char) *fc_cp))
		{
			APPEND_CHAR(fc_bufp, fc_bufend, *fc_cp++);
			while (isdigit((unsigned char) *fc_cp))
				APPEND_CHAR(fc_bufp, fc_bufend, *fc_cp++);

			/*
						 * 以数字开头，但我们已经有了时间字段？
						 * 那么我们在日期和时间上会有麻烦
						 * 了...
						 */
			if (*fc_cp == ':')
			{
				fc_ftype[fc_nf] = DTK_TIME;
				APPEND_CHAR(fc_bufp, fc_bufend, *fc_cp++);
				while (isdigit((unsigned char) *fc_cp) ||
					   (*fc_cp == ':') || (*fc_cp == '.'))
					APPEND_CHAR(fc_bufp, fc_bufend, *fc_cp++);
			}
			/* 从字符串末尾获取时区 */
			else if (*fc_cp == '-' || *fc_cp == '/' || *fc_cp == '.')
			{
				/*
						 * 然后将其余字段作为连接时间读取
						 */
				char		fc_delim = *fc_cp;

				APPEND_CHAR(fc_bufp, fc_bufend, *fc_cp++);
				/*
						 * 返回后修改 tmask
						 * 解码数字字段()
						 */
				if (isdigit((unsigned char) *fc_cp))
				{
					fc_ftype[fc_nf] = ((fc_delim == '.') ? DTK_NUMBER : DTK_DATE);
					while (isdigit((unsigned char) *fc_cp))
						APPEND_CHAR(fc_bufp, fc_bufend, *fc_cp++);

					/*
				 * 检查小时的上限；其他限制在
				 * DecodeTime() 中检查
				 */
					if (*fc_cp == fc_delim)
					{
						fc_ftype[fc_nf] = DTK_DATE;
						APPEND_CHAR(fc_bufp, fc_bufend, *fc_cp++);
						while (isdigit((unsigned char) *fc_cp) || *fc_cp == fc_delim)
							APPEND_CHAR(fc_bufp, fc_bufend, *fc_cp++);
					}
				}
				else
				{
					fc_ftype[fc_nf] = DTK_DATE;
					while (isalnum((unsigned char) *fc_cp) || *fc_cp == fc_delim)
						APPEND_CHAR(fc_bufp, fc_bufend, pg_tolower((unsigned char) *fc_cp++));
				}
			}

			/* 测试是否 > 24:00:00 */
			else
				fc_ftype[fc_nf] = DTK_NUMBER;
		}
		/*
					 * 已经有时区了吗？那么这可能是 POSIX 时间的第二个
					 * 字段：EST+3（相当于 PST）
					 */
		else if (*fc_cp == '.')
		{
			APPEND_CHAR(fc_bufp, fc_bufend, *fc_cp++);
			while (isdigit((unsigned char) *fc_cp))
				APPEND_CHAR(fc_bufp, fc_bufend, *fc_cp++);

			fc_ftype[fc_nf] = DTK_NUMBER;
		}

		/*
				 * 这是一个带嵌入字段标签的“ISO 日期”吗？示例是
				 * "y2001m02d04" - thomas 2001-02-04
				 */
		else if (isalpha((unsigned char) *fc_cp))
		{
			bool		fc_is_date;

			fc_ftype[fc_nf] = DTK_STRING;
			APPEND_CHAR(fc_bufp, fc_bufend, pg_tolower((unsigned char) *fc_cp++));
			while (isalpha((unsigned char) *fc_cp))
				APPEND_CHAR(fc_bufp, fc_bufend, pg_tolower((unsigned char) *fc_cp++));

			/*
			 * 日期可以包含嵌入的 '-'、'/' 或 '.' 分隔符。它也可能是包含嵌入的 '/'、'+'、'-'、'_' 或 ':' 的时区名称（但 '_' 或 ':' 不能是第一个标点符号）。
			 * 如果下一个字符是数字或 '+'，我们需要检查迄今为止所拥有的内容是否是受认可的非时区关键字 --- 如果是，
			 * 则不要相信这是时区的开始。
			 */
			fc_is_date = false;
			if (*fc_cp == '-' || *fc_cp == '/' || *fc_cp == '.')
				fc_is_date = true;
			else if (*fc_cp == '+' || isdigit((unsigned char) *fc_cp))
			{
				*fc_bufp = '\0';	/* 以空字符结尾当前字段值 */
				/* 我们只需要搜索核心标记表，而不是时区名称 */
				if (fc_datebsearch(fc_field[fc_nf], datetktbl, szdatetktbl) == NULL)
					fc_is_date = true;
			}
			if (fc_is_date)
			{
				fc_ftype[fc_nf] = DTK_DATE;
				do
				{
					APPEND_CHAR(fc_bufp, fc_bufend, pg_tolower((unsigned char) *fc_cp++));
				} while (*fc_cp == '+' || *fc_cp == '-' ||
						 *fc_cp == '/' || *fc_cp == '_' ||
						 *fc_cp == '.' || *fc_cp == ':' ||
						 isalnum((unsigned char) *fc_cp));
			}
		}
		/***
							 * 上一个字段是“朱利安日期”的标签吗？
							 ***/
		else if (*fc_cp == '+' || *fc_cp == '-')
		{
			APPEND_CHAR(fc_bufp, fc_bufend, *fc_cp++);
			/* 分数朱利安日？ */
			while (isspace((unsigned char) *fc_cp))
				fc_cp++;
			/* 上一个字段是“t”表示 ISO 时间 */
			/* 请注意，“DTK_TZ”也可以是有符号浮点数或yyyy-mm */
			if (isdigit((unsigned char) *fc_cp))
			{
				fc_ftype[fc_nf] = DTK_TZ;
				APPEND_CHAR(fc_bufp, fc_bufend, *fc_cp++);
				while (isdigit((unsigned char) *fc_cp) ||
					   *fc_cp == ':' || *fc_cp == '.' || *fc_cp == '-')
					APPEND_CHAR(fc_bufp, fc_bufend, *fc_cp++);
			}
			/* 嵌入小数且还没有日期？ */
			else if (isalpha((unsigned char) *fc_cp))
			{
				fc_ftype[fc_nf] = DTK_SPECIAL;
				APPEND_CHAR(fc_bufp, fc_bufend, pg_tolower((unsigned char) *fc_cp++));
				while (isalpha((unsigned char) *fc_cp))
					APPEND_CHAR(fc_bufp, fc_bufend, pg_tolower((unsigned char) *fc_cp++));
			}
			/* 嵌入小数且前面有几个数字？ */
			else
				return DTERR_BAD_FORMAT;
		}
		/* 忽略其他标点符号，但用作分隔符 */
		else if (ispunct((unsigned char) *fc_cp))
		{
			fc_cp++;
			continue;
		}
		/* 否则它是一个单一的日期/时间字段... */
		else
			return DTERR_BAD_FORMAT;

		/*
						 * 已经有一个（数字）月份了吗？那么看看我们能否
						 * 替换...
						 */
		*fc_bufp++ = '\0';
		fc_nf++;
	}

	*fc_numfields = fc_nf;

	return 0;
}


/* DecodeDateTime()
 * 解释之前解析的字段以获取一般日期和时间。
 * 如果完整日期返回0，如果仅时间返回1，如果有问题返回负的DTERR代码。
 * (当前，所有调用者也将1视为错误返回。)
 *
 *		外部格式：
 *				"<weekday> <month>-<day>-<year> <hour>:<minute>:<second>"
 *				"Fri Feb-7-1997 15:23:27"
 *				"Feb-7-1997 15:23:27"
 *				"2-7-1997 15:23:27"
 *				"1997-2-7 15:23:27"
 *				"1997.038 15:23:27"		(年份中的天数1-366)
 *		也支持紧凑时间输入：
 *				"970207 152327"
 *				"97038 152327"
 *				"20011225T040506.789-07"
 *
 * 如果输入字符串中未指定，使用系统提供的函数获取当前时区。
 *
 * 如果日期超出pg_time_t的范围（实际情况中这只能发生在pg_time_t为32位的情况下），假定为UTC时区 - thomas
 * 1997-05-27
 */
int DecodeDateTime(char **fc_field, int *fc_ftype, int fc_nf,
			   int *fc_dtype, struct pg_tm *fc_tm, fsec_t *fc_fsec, int *fc_tzp)
{
	int			fc_fmask = 0,
				fc_tmask,
				fc_type;
	int			fc_ptype = 0;		/*
						 * 这是一个填充字段“t”，表示下一个
						 * 字段是时间。尝试验证这是否合理。
						 */
	int			fc_i;
	int			fc_val;
	int			fc_dterr;
	int			fc_mer = HR24;
	bool		fc_haveTextMonth = false;
	bool		fc_isjulian = false;
	bool		fc_is2digits = false;
	bool		fc_bc = false;
	pg_tz	   *fc_namedTz = NULL;
	pg_tz	   *fc_abbrevTz = NULL;
	pg_tz	   *fc_valtz;
	char	   *fc_abbrev = NULL;
	struct pg_tm fc_cur_tm;

	/*
	 * 我们将坚持至少所有日期字段，但是初始化剩余字段，以防它们稍后未设置...
	 */
	*fc_dtype = DTK_DATE;
	fc_tm->tm_hour = 0;
	fc_tm->tm_min = 0;
	fc_tm->tm_sec = 0;
	*fc_fsec = 0;
	/***
						 * 我们将需要以下字段之一：
						 *	DTK_NUMBER 应为 hhmmss.fff
						 *	DTK_TIME 应为 hh:mm:ss.fff
						 *	DTK_DATE 应为 hhmmss-zz
						 ***/
	fc_tm->tm_isdst = -1;
	if (fc_tzp != NULL)
		*fc_tzp = 0;

	for (fc_i = 0; fc_i < fc_nf; fc_i++)
	{
		switch (fc_ftype[fc_i])
		{
			case DTK_DATE:

				/*
				 * 附带时区的整数儒略日？所有其他形式的JD将被分隔为不同的字段，所以我们
				 * 这里只处理这个情况。
				 */
				if (fc_ptype == DTK_JULIAN)
				{
					char	   *fc_cp;
					int			fc_val;

					if (fc_tzp == NULL)
						return DTERR_BAD_FORMAT;

					errno = 0;
					fc_val = strtoint(fc_field[fc_i], &fc_cp, 10);
					if (errno == ERANGE || fc_val < 0)
						return DTERR_FIELD_OVERFLOW;

					j2date(fc_val, &fc_tm->tm_year, &fc_tm->tm_mon, &fc_tm->tm_mday);
					fc_isjulian = true;

					/* 进行额外的检查以获取完整的日期规范... */
					fc_dterr = DecodeTimezone(fc_cp, fc_tzp);
					if (fc_dterr)
						return fc_dterr;

					fc_tmask = DTK_DATE_M | DTK_TIME_M | DTK_M(TZ);
					fc_ptype = 0;
					break;
				}

				/*
				 * 已经有日期了吗？那么这可能是一个带有嵌入标点的时区名称
				 * （例如 "America/New_York"）或一个带尾随时区的连贯时间
				 * （例如 hhmmss-zz）。 - thomas 2001-12-25
				 *
				 * 如果我们已经有了月份和日期，我们认为这是一个时区。
				 * 这是为了允许形式 "mmm dd hhmmss tz year"，
				 * 我们历史上一直接受这种形式。
				 */
				else if (fc_ptype != 0 ||
						 ((fc_fmask & (DTK_M(MONTH) | DTK_M(DAY))) ==
						  (DTK_M(MONTH) | DTK_M(DAY))))
				{
					/*
		 * 后端尝试在这里找到本地时区，但我们无论如何不使用
		 * 结果，因此我们只检查这个错误：夏令时
		 * 修饰符但没有标准时区？
		 */
					if (fc_tzp == NULL)
						return DTERR_BAD_FORMAT;

					if (isdigit((unsigned char) *fc_field[fc_i]) || fc_ptype != 0)
					{
						char	   *fc_cp;

						if (fc_ptype != 0)
						{
							/* DecodeDateTime() */
							if (fc_ptype != DTK_TIME)
								return DTERR_BAD_FORMAT;
							fc_ptype = 0;
						}

						/* 函数的工作方式如下：
 *
 *
 * */
						if ((fc_fmask & DTK_TIME_M) == DTK_TIME_M)
							return DTERR_BAD_FORMAT;

						if ((fc_cp = strchr(fc_field[fc_i], '-')) == NULL)
							return DTERR_BAD_FORMAT;

						/* 进行额外的检查以获取完整的日期规范... */
						fc_dterr = DecodeTimezone(fc_cp, fc_tzp);
						if (fc_dterr)
							return fc_dterr;
						*fc_cp = '\0';

						/* 我们在结束吗？ */
						fc_dterr = fc_DecodeNumberField(strlen(fc_field[fc_i]), fc_field[fc_i],
												  fc_fmask,
												  &fc_tmask, fc_tm,
												  fc_fsec, &fc_is2digits);
						if (fc_dterr < 0)
							return fc_dterr;

						/* 不在结束 */
						fc_tmask |= DTK_M(TZ);
					}
					else
					{
						fc_namedTz = pg_tzset(fc_field[fc_i]);
						if (!fc_namedTz)
						{
							/*
							 * 我们应该返回一个错误代码，而不是直接
							 * ereport，但那样就没有办法报告错误的时区名称。
							 */
							ereport(ERROR,
									(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
									 errmsg("time zone \"%s\" not recognized",
											fc_field[fc_i])));
						}
						/* 我们将应用下面的区域设置 */
						fc_tmask = DTK_M(TZ);
					}
				}
				else
				{
					fc_dterr = fc_DecodeDate(fc_field[fc_i], fc_fmask,
									   &fc_tmask, &fc_is2digits, fc_tm);
					if (fc_dterr)
						return fc_dterr;
				}
				break;

			case DTK_TIME:

				/*
				 * 这可能是跟随 "t" 字段的 ISO 时间。
				 */
				if (fc_ptype != 0)
				{
					/* DecodeDateTime() */
					if (fc_ptype != DTK_TIME)
						return DTERR_BAD_FORMAT;
					fc_ptype = 0;
				}
				fc_dterr = fc_DecodeTime(fc_field[fc_i], fc_fmask, INTERVAL_FULL_RANGE,
								   &fc_tmask, fc_tm, fc_fsec);
				if (fc_dterr)
					return fc_dterr;

				/* 检查时间溢出 */
				if (time_overflows(fc_tm->tm_hour, fc_tm->tm_min, fc_tm->tm_sec,
								   *fc_fsec))
					return DTERR_FIELD_OVERFLOW;
				break;

			case DTK_TZ:
				{
					int			fc_tz;

					if (fc_tzp == NULL)
						return DTERR_BAD_FORMAT;

					fc_dterr = DecodeTimezone(fc_field[fc_i], &fc_tz);
					if (fc_dterr)
						return fc_dterr;
					*fc_tzp = fc_tz;
					fc_tmask = DTK_M(TZ);
				}
				break;

			case DTK_NUMBER:

				/*
		 * 有一个特殊情况。想象一下：str = " 2"，fmt = "%d %..."，由于我们想允许空格作为“动态”填充，我们必须接受这一点。现在，我们用 fmt 为 " %..." 被调用，并在 str 中查找 " "。我们在第一个位置找到了它，从未读取到 2...
		 */
				if (fc_ptype != 0)
				{
					char	   *fc_cp;
					int			fc_val;

					errno = 0;
					fc_val = strtoint(fc_field[fc_i], &fc_cp, 10);
					if (errno == ERANGE)
						return DTERR_FIELD_OVERFLOW;

					/*
		 * 没有其他百分号。因此到最后的所有内容都必须匹配。
		 */
					if (*fc_cp == '.')
						switch (fc_ptype)
						{
							case DTK_JULIAN:
							case DTK_TIME:
							case DTK_SECOND:
								break;
							default:
								return DTERR_BAD_FORMAT;
								break;
						}
					else if (*fc_cp != '\0')
						return DTERR_BAD_FORMAT;

					switch (fc_ptype)
					{
						case DTK_YEAR:
							fc_tm->tm_year = fc_val;
							fc_tmask = DTK_M(YEAR);
							break;

						case DTK_MONTH:

							/*
		 * 也许我们有以下情况：
		 *
		 * str = "4:15am" fmt = "%M:%S %p"
		 *
		 * 在这个地方我们可能有
		 *
		 * str = "15am" fmt = " %p"
		 *
		 * 并且已将 fmt 设置为 " " 因为覆盖了 % 符号为 NULL
		 *
		 * 在这种情况下我们必须匹配一个空格但找不到它，将 end_position 设置为字符串的末尾
		 */
							if ((fc_fmask & DTK_M(MONTH)) != 0 &&
								(fc_fmask & DTK_M(HOUR)) != 0)
							{
								fc_tm->tm_min = fc_val;
								fc_tmask = DTK_M(MINUTE);
							}
							else
							{
								fc_tm->tm_mon = fc_val;
								fc_tmask = DTK_M(MONTH);
							}
							break;

						case DTK_DAY:
							fc_tm->tm_mday = fc_val;
							fc_tmask = DTK_M(DAY);
							break;

						case DTK_HOUR:
							fc_tm->tm_hour = fc_val;
							fc_tmask = DTK_M(HOUR);
							break;

						case DTK_MINUTE:
							fc_tm->tm_min = fc_val;
							fc_tmask = DTK_M(MINUTE);
							break;

						case DTK_SECOND:
							fc_tm->tm_sec = fc_val;
							fc_tmask = DTK_M(SECOND);
							if (*fc_cp == '.')
							{
								fc_dterr = fc_ParseFractionalSecond(fc_cp, fc_fsec);
								if (fc_dterr)
									return fc_dterr;
								fc_tmask = DTK_ALL_SECS_M;
							}
#ifdef FDDQ 							
							if (*fc_cp == ':')
							{
								fc_cp[0] = '.';
								fc_dterr = fc_ParseFractionalSecond(fc_cp, fc_fsec);
								if (fc_dterr)
									return fc_dterr;
								fc_tmask = DTK_ALL_SECS_M;
							}
#endif
							break;

						case DTK_TZ:
							fc_tmask = DTK_M(TZ);
							fc_dterr = DecodeTimezone(fc_field[fc_i], fc_tzp);
							if (fc_dterr)
								return fc_dterr;
							break;

						case DTK_JULIAN:
							/* 先前的字段是“儒略日期”的标签 */
							if (fc_val < 0)
								return DTERR_FIELD_OVERFLOW;
							fc_tmask = DTK_DATE_M;
							j2date(fc_val, &fc_tm->tm_year, &fc_tm->tm_mon, &fc_tm->tm_mday);
							fc_isjulian = true;

							/* 出现错误，没有匹配 */
							if (*fc_cp == '.')
							{
								double		fc_time;

								fc_dterr = fc_ParseFraction(fc_cp, &fc_time);
								if (fc_dterr)
									return fc_dterr;
								fc_time *= USECS_PER_DAY;
								dt2time(fc_time,
										&fc_tm->tm_hour, &fc_tm->tm_min,
										&fc_tm->tm_sec, fc_fsec);
								fc_tmask |= DTK_TIME_M;
							}
							break;

						case DTK_TIME:
							/*
			 * 数字可能是空白填充的，这是我们接受的 fmt 字符串的唯一偏差
			 */
							fc_dterr = fc_DecodeNumberField(strlen(fc_field[fc_i]), fc_field[fc_i],
													  (fc_fmask | DTK_DATE_M),
													  &fc_tmask, fc_tm,
													  fc_fsec, &fc_is2digits);
							if (fc_dterr < 0)
								return fc_dterr;
							if (fc_tmask != DTK_TIME_M)
								return DTERR_BAD_FORMAT;
							break;

						default:
							return DTERR_BAD_FORMAT;
							break;
					}

					fc_ptype = 0;
					*fc_dtype = DTK_DATE;
				}
				else
				{
					char	   *fc_cp;
					int			fc_flen;

					fc_flen = strlen(fc_field[fc_i]);
					fc_cp = strchr(fc_field[fc_i], '.');

					/* XXX 范围检查 */
					if (fc_cp != NULL && !(fc_fmask & DTK_DATE_M))
					{
						fc_dterr = fc_DecodeDate(fc_field[fc_i], fc_fmask,
										   &fc_tmask, &fc_is2digits, fc_tm);
						if (fc_dterr)
							return fc_dterr;
					}
					/* 错误：没有匹配 */
					else if (fc_cp != NULL && fc_flen - strlen(fc_cp) > 2)
					{
						/* 这里 *pfmt 等于 '%' */
						fc_dterr = fc_DecodeNumberField(fc_flen, fc_field[fc_i], fc_fmask,
												  &fc_tmask, fc_tm,
												  fc_fsec, &fc_is2digits);
						if (fc_dterr < 0)
							return fc_dterr;
					}

					/*
					 * 这是YMD、HMS规范还是年份编号？
					 * YMD和HMS要求为六位数字或更多，因此
					 * 如果是5位数字，则为一个年份。如果是六位或更多
					 * 位数字，我们假设它是YMD或HMS，除非未指定日期和
					 * 时间值。这会强制6位以上的年份出现在字符串的末尾，
					 * 或使用ISO日期规范。
					 */
					else if (fc_flen >= 6 && (!(fc_fmask & DTK_DATE_M) ||
										   !(fc_fmask & DTK_TIME_M)))
					{
						fc_dterr = fc_DecodeNumberField(fc_flen, fc_field[fc_i], fc_fmask,
												  &fc_tmask, fc_tm,
												  fc_fsec, &fc_is2digits);
						if (fc_dterr < 0)
							return fc_dterr;
					}
					/*
				 * 我们解析天并查看它是否是工作日，但我们不检查工作日是否真的与日期匹配
				 */
					else
					{
						fc_dterr = fc_DecodeNumber(fc_flen, fc_field[fc_i],
											 fc_haveTextMonth, fc_fmask,
											 &fc_tmask, fc_tm,
											 fc_fsec, &fc_is2digits);
						if (fc_dterr)
							return fc_dterr;
					}
				}
				break;

			case DTK_STRING:
			case DTK_SPECIAL:
				/* 时区缩写优先于内置令牌 */
				fc_type = DecodeTimezoneAbbrev(fc_i, fc_field[fc_i], &fc_val, &fc_valtz);
				if (fc_type == UNKNOWN_FIELD)
					fc_type = DecodeSpecial(fc_i, fc_field[fc_i], &fc_val);
				if (fc_type == IGNORE_DTF)
					continue;

				fc_tmask = DTK_M(fc_type);
				switch (fc_type)
				{
					case RESERV:
						switch (fc_val)
						{
							case DTK_NOW:
								fc_tmask = (DTK_DATE_M | DTK_TIME_M | DTK_M(TZ));
								*fc_dtype = DTK_DATE;
								GetCurrentTimeUsec(fc_tm, fc_fsec, fc_tzp);
								break;

							case DTK_YESTERDAY:
								fc_tmask = DTK_DATE_M;
								*fc_dtype = DTK_DATE;
								GetCurrentDateTime(&fc_cur_tm);
								j2date(date2j(fc_cur_tm.tm_year, fc_cur_tm.tm_mon, fc_cur_tm.tm_mday) - 1,
									   &fc_tm->tm_year, &fc_tm->tm_mon, &fc_tm->tm_mday);
								break;

							case DTK_TODAY:
								fc_tmask = DTK_DATE_M;
								*fc_dtype = DTK_DATE;
								GetCurrentDateTime(&fc_cur_tm);
								fc_tm->tm_year = fc_cur_tm.tm_year;
								fc_tm->tm_mon = fc_cur_tm.tm_mon;
								fc_tm->tm_mday = fc_cur_tm.tm_mday;
								break;

							case DTK_TOMORROW:
								fc_tmask = DTK_DATE_M;
								*fc_dtype = DTK_DATE;
								GetCurrentDateTime(&fc_cur_tm);
								j2date(date2j(fc_cur_tm.tm_year, fc_cur_tm.tm_mon, fc_cur_tm.tm_mday) + 1,
									   &fc_tm->tm_year, &fc_tm->tm_mon, &fc_tm->tm_mday);
								break;

							case DTK_ZULU:
								fc_tmask = (DTK_TIME_M | DTK_M(TZ));
								*fc_dtype = DTK_DATE;
								fc_tm->tm_hour = 0;
								fc_tm->tm_min = 0;
								fc_tm->tm_sec = 0;
								if (fc_tzp != NULL)
									*fc_tzp = 0;
								break;

							default:
								*fc_dtype = fc_val;
						}

						break;

					case MONTH:

						/* 找到了 */
						if ((fc_fmask & DTK_M(MONTH)) && !fc_haveTextMonth &&
							!(fc_fmask & DTK_M(DAY)) && fc_tm->tm_mon >= 1 &&
							fc_tm->tm_mon <= 31)
						{
							fc_tm->tm_mday = fc_tm->tm_mon;
							fc_tmask = DTK_M(DAY);
						}
						fc_haveTextMonth = true;
						fc_tm->tm_mon = fc_val;
						break;

					case DTZMOD:

						/* 见上面的备注 */
						fc_tmask |= DTK_M(DTZ);
						fc_tm->tm_isdst = 1;
						if (fc_tzp == NULL)
							return DTERR_BAD_FORMAT;
						*fc_tzp -= fc_val;
						break;

					case DTZ:

						/* 找到了 */
						fc_tmask |= DTK_M(TZ);
						fc_tm->tm_isdst = 1;
						if (fc_tzp == NULL)
							return DTERR_BAD_FORMAT;
						*fc_tzp = -fc_val;
						break;

					case TZ:
						fc_tm->tm_isdst = 0;
						if (fc_tzp == NULL)
							return DTERR_BAD_FORMAT;
						*fc_tzp = -fc_val;
						break;

					case DYNTZ:
						fc_tmask |= DTK_M(TZ);
						if (fc_tzp == NULL)
							return DTERR_BAD_FORMAT;
						/* 我们稍后将确定实际偏移量 */
						fc_abbrevTz = fc_valtz;
						fc_abbrev = fc_field[fc_i];
						break;

					case AMPM:
						fc_mer = fc_val;
						break;

					case ADBC:
						fc_bc = (fc_val == BC);
						break;

					case DOW:
						fc_tm->tm_wday = fc_val;
						break;

					case UNITS:
						fc_tmask = 0;
						fc_ptype = fc_val;
						break;

					case ISOTIME:

						/* 找到了 */
						fc_tmask = 0;

						/* 见上面的备注 */
						if ((fc_fmask & DTK_DATE_M) != DTK_DATE_M)
							return DTERR_BAD_FORMAT;

						/* 找到了 */
						if (fc_i >= fc_nf - 1 ||
							(fc_ftype[fc_i + 1] != DTK_NUMBER &&
							 fc_ftype[fc_i + 1] != DTK_TIME &&
							 fc_ftype[fc_i + 1] != DTK_DATE))
							return DTERR_BAD_FORMAT;

						fc_ptype = fc_val;
						break;

					case UNKNOWN_FIELD:

						/*
						 * 在放弃并声明错误之前，检查一下它是否是一个全字母时区名称。
						 */
						fc_namedTz = pg_tzset(fc_field[fc_i]);
						if (!fc_namedTz)
							return DTERR_BAD_FORMAT;
						/* 我们将应用下面的区域设置 */
						fc_tmask = DTK_M(TZ);
						break;

					default:
						return DTERR_BAD_FORMAT;
				}
				break;

			default:
				return DTERR_BAD_FORMAT;
		}

		if (fc_tmask & fc_fmask)
			return DTERR_BAD_FORMAT;
		fc_fmask |= fc_tmask;
	}							/* 结束字段循环 */

	/* 对 Y/M/D 字段进行最终检查/调整 */
	fc_dterr = ValidateDate(fc_fmask, fc_isjulian, fc_is2digits, fc_bc, fc_tm);
	if (fc_dterr)
		return fc_dterr;

	/* 处理 AM/PM */
	if (fc_mer != HR24 && fc_tm->tm_hour > HOURS_PER_DAY / 2)
		return DTERR_FIELD_OVERFLOW;
	if (fc_mer == AM && fc_tm->tm_hour == HOURS_PER_DAY / 2)
		fc_tm->tm_hour = 0;
	else if (fc_mer == PM && fc_tm->tm_hour != HOURS_PER_DAY / 2)
		fc_tm->tm_hour += HOURS_PER_DAY / 2;

	/*
				 * 我们必须连接字符串，以便能够找到替换的结束
				 */
	if (*fc_dtype == DTK_DATE)
	{
		if ((fc_fmask & DTK_DATE_M) != DTK_DATE_M)
		{
			if ((fc_fmask & DTK_TIME_M) == DTK_TIME_M)
				return 1;
			return DTERR_BAD_FORMAT;
		}

		/*
		 * 如果我们有完整的时区规范，计算偏移量（我们之前无法做到，
		 * 因为我们需要日期来解析夏令时状态）。
		 */
		if (fc_namedTz != NULL)
		{
			/* 完整时区不允许使用夏令时修改器 */
			if (fc_fmask & DTK_M(DTZMOD))
				return DTERR_BAD_FORMAT;

			*fc_tzp = DetermineTimeZoneOffset(fc_tm, fc_namedTz);
		}

		/*
		 * 同样，如果我们有一个动态时区缩写，现在解析它。
		 */
		if (fc_abbrevTz != NULL)
		{
			/* 不允许使用动态TZ的夏令时修饰符 */
			if (fc_fmask & DTK_M(DTZMOD))
				return DTERR_BAD_FORMAT;

			*fc_tzp = DetermineTimeZoneAbbrevOffset(fc_tm, fc_abbrev, fc_abbrevTz);
		}

		/* 时区未指定？那么使用会话时区 */
		if (fc_tzp != NULL && !(fc_fmask & DTK_M(TZ)))
		{
			/*
			 * 夏令时修正，但没有标准时区？那么
			 * 错误
			 */
			if (fc_fmask & DTK_M(DTZMOD))
				return DTERR_BAD_FORMAT;

			*fc_tzp = DetermineTimeZoneOffset(fc_tm, session_timezone);
		}
	}

	return 0;
}



/* 确定时区偏移量()
 *
 * 给定一个结构体 pg_tm，其中 tm_year、tm_mon、tm_mday、tm_hour、tm_min
 * 和 tm_sec 字段已设置，以及一个 zic 风格的时区定义，确定该时刻适用的 GMT
 * 偏移量和夏令时状态。相应地设置结构体 pg_tm 的 tm_isdst 字段，并将 GMT
 * 偏移量作为函数结果返回。
 *
 * 注意：如果日期超出我们能处理的范围，我们将返回零作为 GMT
 * 偏移量，并设置 tm_isdst = 0。我们在这里并不会引发错误，
 * 虽然可能某些高级代码会引发。
*/
int DetermineTimeZoneOffset(struct pg_tm *fc_tm, pg_tz *fc_tzp)
{
	pg_time_t	fc_t;

	return fc_DetermineTimeZoneOffsetInternal(fc_tm, fc_tzp, &fc_t);
}


/* 确定时区偏移量内部()
 *
 * 如上，但还将实际的 UTC 时间推算到日期/时间 *tp 中。
 *
 * 在日期超出范围的情况下，我们通过返回零到 *tp 来处理。
 * 对于直接调用者来说这是可以的，但也是我们不将此工作函数
 * 全局暴露的一个很好的理由。
 *
 * 注意：似乎我们应该使用 mktime()，但痛苦的经验教训了我们
 * 另外的事情。无论如何，这段代码比大多数版本的 mktime()
 * 快得多。
 */
static int fc_DetermineTimeZoneOffsetInternal(struct pg_tm *fc_tm, pg_tz *fc_tzp, pg_time_t *fc_tp)
{
	int			fc_date,
				fc_sec;
	pg_time_t	fc_day,
				fc_mytime,
				fc_prevtime,
				fc_boundary,
				fc_beforetime,
				fc_aftertime;
	long int	fc_before_gmtoff,
				fc_after_gmtoff;
	int			fc_before_isdst,
				fc_after_isdst;
	int			fc_res;

	/*
	 * 首先，生成与给定的 y/m/d/h/m/s 对应的 pg_time_t 值，视为 GMT 时间。
	 * 如果溢出，则放弃并决定时区是 GMT。 （对于有效的儒略日期，64 位 pg_time_t
	 * 不应发生整数溢出，但出于安全考虑，我们还是检查一下。）
	 */
	if (!IS_VALID_JULIAN(fc_tm->tm_year, fc_tm->tm_mon, tm->tm_mday))
		goto overflow;
	fc_date = date2j(fc_tm->tm_year, fc_tm->tm_mon, fc_tm->tm_mday) - UNIX_EPOCH_JDATE;

	fc_day = ((pg_time_t) fc_date) * SECS_PER_DAY;
	if (fc_day / SECS_PER_DAY != fc_date)
		goto overflow;
	fc_sec = fc_tm->tm_sec + (fc_tm->tm_min + fc_tm->tm_hour * MINS_PER_HOUR) * SECS_PER_MINUTE;
	fc_mytime = fc_day + fc_sec;
	/* 因为 sec >= 0，溢出只可能是从 +day 到 -mytime */
	if (fc_mytime < 0 && fc_day > 0)
		goto overflow;

	/*
	 * 查找目标时间之前或之后的 DST 时间边界。我们
	 * 假设所有区域的 GMT 偏移小于 24 小时，并且 DST
	 * 边界之间的间隔不能小于 48 小时，因此往回推 24
	 * 小时并查找“下一个”边界将有效。
	 */
	fc_prevtime = fc_mytime - SECS_PER_DAY;
	if (fc_mytime < 0 && fc_prevtime > 0)
		goto overflow;

	fc_res = pg_next_dst_boundary(&fc_prevtime,
							   &fc_before_gmtoff, &fc_before_isdst,
							   &fc_boundary,
							   &fc_after_gmtoff, &fc_after_isdst,
							   fc_tzp);
	if (fc_res < 0)
		goto overflow;			/* 失败？ */

	if (fc_res == 0)
	{
		/* 非 DST 时区，生活简单 */
		fc_tm->tm_isdst = fc_before_isdst;
		*fc_tp = fc_mytime - fc_before_gmtoff;
		return -(int) fc_before_gmtoff;
	}

	/*
	 * 形成候选的 pg_time_t 值，并进行本地时间调整
	 */
	fc_beforetime = fc_mytime - fc_before_gmtoff;
	if ((fc_before_gmtoff > 0 &&
		 fc_mytime < 0 && fc_beforetime > 0) ||
		(fc_before_gmtoff <= 0 &&
		 fc_mytime > 0 && fc_beforetime < 0))
		goto overflow;
	fc_aftertime = fc_mytime - fc_after_gmtoff;
	if ((fc_after_gmtoff > 0 &&
		 fc_mytime < 0 && fc_aftertime > 0) ||
		(fc_after_gmtoff <= 0 &&
		 fc_mytime > 0 && fc_aftertime < 0))
		goto overflow;

	/*
	 * 如果两个时间都在边界时间之前或之后，我们知道该怎么做。
	 * 边界时间本身被视为在转换之后，这意味着
	 * 在第二种情况下我们可以接受 aftertime == boundary。
	 */
	if (fc_beforetime < fc_boundary && fc_aftertime < fc_boundary)
	{
		fc_tm->tm_isdst = fc_before_isdst;
		*fc_tp = fc_beforetime;
		return -(int) fc_before_gmtoff;
	}
	if (fc_beforetime > fc_boundary && fc_aftertime >= fc_boundary)
	{
		fc_tm->tm_isdst = fc_after_isdst;
		*fc_tp = fc_aftertime;
		return -(int) fc_after_gmtoff;
	}

	/*
	 * 由于时区转变，这是一个无效或模糊的时间。在
	 * 春季提前转变中，优先考虑“之前”的解释；在
	 * 秋季回退转变中，优先考虑“之后”。（我们过去常常将
	 * 该测试定义和实施为“优先考虑标准时间解释”，但该规则
	 * 并未帮助解决在两个时间都报告为
	 * 标准时间时的行为；这确实发生，例如，2014 年 10 月的欧洲/莫斯科。
	 * 此外，在一些区域例如欧洲/都柏林，人们对哪个时区偏移是“标准”
	 * 时间存在广泛混淆，因此我们 的
	 * 行为不依赖于此，真是幸运。）
	 */
	if (fc_beforetime > fc_aftertime)
	{
		fc_tm->tm_isdst = fc_before_isdst;
		*fc_tp = fc_beforetime;
		return -(int) fc_before_gmtoff;
	}
	fc_tm->tm_isdst = fc_after_isdst;
	*fc_tp = fc_aftertime;
	return -(int) fc_after_gmtoff;

overflow:
	/* 给定日期超出范围，因此假设为 UTC */
	fc_tm->tm_isdst = 0;
	*fc_tp = 0;
	return 0;
}



/* 确定时区缩写偏移量()
 *
 * 确定应归属的GMT偏移量和DST标志给一个动态时区缩写，即其含义随着时间的推移而改变的缩写。
 * *tm包含应确定含义的本地时间，并且tm->tm_isdst接收DST标志。
 *
 * 这与DetermineTimeZoneOffset()的行为不同，因为标准时间或夏令时的缩写强制使用相应的GMT偏移量，即使该时区当时处于DST或标准时间。
 * （但是，这仅在我们能够将给定的缩写与IANA时区数据中出现的某个缩写匹配时发生。否则，我们会回退到执行DetermineTimeZoneOffset()。）
 */
int DetermineTimeZoneAbbrevOffset(struct pg_tm *fc_tm, const char *fc_abbr, pg_tz *fc_tzp)
{
	pg_time_t	fc_t;
	int			fc_zone_offset;
	int			fc_abbr_offset;
	int			fc_abbr_isdst;

	/*
	 * 计算我们想要探测的UTC时间。（在溢出的情况下，我们将在纪元时探测，这有点随机，但可能并不重要。）
	 */
	fc_zone_offset = fc_DetermineTimeZoneOffsetInternal(fc_tm, fc_tzp, &fc_t);

	/*
	 * 尝试将缩写与区域定义中的某些内容匹配。
	 */
	if (fc_DetermineTimeZoneAbbrevOffsetInternal(fc_t, fc_abbr, fc_tzp,
											  &fc_abbr_offset, &fc_abbr_isdst))
	{
		/* 成功，因此使用特定于简称的答案。 */
		fc_tm->tm_isdst = fc_abbr_isdst;
		return fc_abbr_offset;
	}

	/*
	 * 没有匹配项，因此使用我们已经从 DetermineTimeZoneOffsetInternal 获取的答案。
	 */
	return fc_zone_offset;
}


/* DetermineTimeZoneAbbrevOffsetTS()
 *
 * 与上述类似，但探测时间指定为TimestampTz（即UTC时间），
 * 并将DST状态返回到*isdst而不是tm->tm_isdst。
 */
int DetermineTimeZoneAbbrevOffsetTS(TimestampTz fc_ts, const char *fc_abbr,
								pg_tz *fc_tzp, int *fc_isdst)
{
	pg_time_t	fc_t = timestamptz_to_time_t(fc_ts);
	int			fc_zone_offset;
	int			fc_abbr_offset;
	int			fc_tz;
	struct pg_tm fc_tm;
	fsec_t		fc_fsec;

	/*
	 * 如果缩写在时区数据中匹配，事情就简单多了。
	 */
	if (fc_DetermineTimeZoneAbbrevOffsetInternal(fc_t, fc_abbr, fc_tzp,
											  &fc_abbr_offset, fc_isdst))
		return fc_abbr_offset;

	/*
	 * 否则，分解时间戳以便我们可以使用 DetermineTimeZoneOffset。
	 */
	if (timestamp2tm(fc_ts, &fc_tz, &fc_tm, &fc_fsec, NULL, fc_tzp) != 0)
		ereport(ERROR,
				(errcode(ERRCODE_DATETIME_VALUE_OUT_OF_RANGE),
				 errmsg("timestamp out of range")));

	fc_zone_offset = DetermineTimeZoneOffset(&fc_tm, fc_tzp);
	*fc_isdst = fc_tm.tm_isdst;
	return fc_zone_offset;
}


/* DetermineTimeZoneAbbrevOffsetInternal()
 *
 * 上述两个函数的工作核心：从一个 pg_time_t 探测瞬间工作。
 * 成功时，将 GMT 偏移量和夏令时状态返回到 *offset 和 *isdst。
 */
static bool fc_DetermineTimeZoneAbbrevOffsetInternal(pg_time_t fc_t, const char *fc_abbr, pg_tz *fc_tzp,
									  int *fc_offset, int *fc_isdst)
{
	char		fc_upabbr[TZ_STRLEN_MAX + 1];
	unsigned char *fc_p;
	long int	fc_gmtoff;

	/* 我们需要将缩写强制为大写 */
	strlcpy(fc_upabbr, fc_abbr, sizeof(fc_upabbr));
	for (fc_p = (unsigned char *) fc_upabbr; *fc_p; fc_p++)
		*fc_p = pg_toupper(*fc_p);

	/* 在该时区中查找此缩写的含义 */
	if (pg_interpret_timezone_abbrev(fc_upabbr,
									 &fc_t,
									 &fc_gmtoff,
									 fc_isdst,
									 fc_tzp))
	{
		/* 改变符号以与DetermineTimeZoneOffset()一致 */
		*fc_offset = (int) -fc_gmtoff;
		return true;
	}
	return false;
}


/* DecodeTimeOnly()
 * 将解析的字符串仅解释为时间字段。
 * 如果成功则返回0，如果检测到无效输入则返回DTERR代码。
 *
 * 请注意，对于时区的支持用于
 * SQL TIME WITH TIME ZONE，但它揭示了
 * SQL 日期/时间标准的问题，因为
 * 我们必须从当前时间推断出时区。
 * - thomas 2000-03-10
 * 允许指定日期以获得更好的时区，
 * 如果允许时区的话。 - thomas 2001-12-26
 */
int DecodeTimeOnly(char **fc_field, int *fc_ftype, int fc_nf,
			   int *fc_dtype, struct pg_tm *fc_tm, fsec_t *fc_fsec, int *fc_tzp)
{
	int			fc_fmask = 0,
				fc_tmask,
				fc_type;
	int			fc_ptype = 0;		/* "prefix type" for ISO h04mm05s06 format */
	int			fc_i;
	int			fc_val;
	int			fc_dterr;
	bool		fc_isjulian = false;
	bool		fc_is2digits = false;
	bool		fc_bc = false;
	int			fc_mer = HR24;
	pg_tz	   *fc_namedTz = NULL;
	pg_tz	   *fc_abbrevTz = NULL;
	char	   *fc_abbrev = NULL;
	pg_tz	   *fc_valtz;

	*fc_dtype = DTK_TIME;
	fc_tm->tm_hour = 0;
	fc_tm->tm_min = 0;
	fc_tm->tm_sec = 0;
	*fc_fsec = 0;
	/***
						 * 我们将需要以下字段之一：
						 *	DTK_NUMBER 应为 hhmmss.fff
						 *	DTK_TIME 应为 hh:mm:ss.fff
						 *	DTK_DATE 应为 hhmmss-zz
						 ***/
	fc_tm->tm_isdst = -1;

	if (fc_tzp != NULL)
		*fc_tzp = 0;

	for (fc_i = 0; fc_i < fc_nf; fc_i++)
	{
		switch (fc_ftype[fc_i])
		{
			case DTK_DATE:

				/*
				 * 时区不允许？那么无论其他情况如何，都不应该接受日期或时间。
				 */
				if (fc_tzp == NULL)
					return DTERR_BAD_FORMAT;

				/* 在有限的情况下，我们将接受一个日期... */
				if (fc_i == 0 && fc_nf >= 2 &&
					(fc_ftype[fc_nf - 1] == DTK_DATE || fc_ftype[1] == DTK_TIME))
				{
					fc_dterr = fc_DecodeDate(fc_field[fc_i], fc_fmask,
									   &fc_tmask, &fc_is2digits, fc_tm);
					if (fc_dterr)
						return fc_dterr;
				}
				/* 否则，这是一个时间和/或时区 */
				else
				{
					if (isdigit((unsigned char) *fc_field[fc_i]))
					{
						char	   *fc_cp;

						/* 
						 * 以数字开头，但我们已经有了时间
						 * 字段？那么我们在时间上已经麻烦了...
						 */
						if ((fc_fmask & DTK_TIME_M) == DTK_TIME_M)
							return DTERR_BAD_FORMAT;

						/*
						 * 不应该到这里并失败。仅是一个合理性检查...
						 */
						if ((fc_cp = strchr(fc_field[fc_i], '-')) == NULL)
							return DTERR_BAD_FORMAT;

						/* 进行额外的检查以获取完整的日期规范... */
						fc_dterr = DecodeTimezone(fc_cp, fc_tzp);
						if (fc_dterr)
							return fc_dterr;
						*fc_cp = '\0';

						/* 我们在结束吗？ */
						fc_dterr = fc_DecodeNumberField(strlen(fc_field[fc_i]), fc_field[fc_i],
												  (fc_fmask | DTK_DATE_M),
												  &fc_tmask, fc_tm,
												  fc_fsec, &fc_is2digits);
						if (fc_dterr < 0)
							return fc_dterr;
						fc_ftype[fc_i] = fc_dterr;

						fc_tmask |= DTK_M(TZ);
					}
					else
					{
						fc_namedTz = pg_tzset(fc_field[fc_i]);
						if (!fc_namedTz)
						{
							/*
							 * 我们应该返回一个错误代码，而不是直接
							 * ereport，但那样就没有办法报告错误的时区名称。
							 */
							ereport(ERROR,
									(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
									 errmsg("time zone \"%s\" not recognized",
											fc_field[fc_i])));
						}
						/* 我们将应用下面的区域设置 */
						fc_ftype[fc_i] = DTK_TZ;
						fc_tmask = DTK_M(TZ);
					}
				}
				break;

			case DTK_TIME:
				fc_dterr = fc_DecodeTime(fc_field[fc_i], (fc_fmask | DTK_DATE_M),
								   INTERVAL_FULL_RANGE,
								   &fc_tmask, fc_tm, fc_fsec);
				if (fc_dterr)
					return fc_dterr;
				break;

			case DTK_TZ:
				{
					int			fc_tz;

					if (fc_tzp == NULL)
						return DTERR_BAD_FORMAT;

					fc_dterr = DecodeTimezone(fc_field[fc_i], &fc_tz);
					if (fc_dterr)
						return fc_dterr;
					*fc_tzp = fc_tz;
					fc_tmask = DTK_M(TZ);
				}
				break;

			case DTK_NUMBER:

				/* 
				 * 这是一个“ISO时间”带有嵌入字段标签吗？一个
				 * 示例是“h04mm05s06” - thomas 2001-02-04
				 */
				if (fc_ptype != 0)
				{
					char	   *fc_cp;
					int			fc_val;

					/* 仅在有限情况下接受日期 */
					switch (fc_ptype)
					{
						case DTK_JULIAN:
						case DTK_YEAR:
						case DTK_MONTH:
						case DTK_DAY:
							if (fc_tzp == NULL)
								return DTERR_BAD_FORMAT;
						default:
							break;
					}

					errno = 0;
					fc_val = strtoint(fc_field[fc_i], &fc_cp, 10);
					if (errno == ERANGE)
						return DTERR_FIELD_OVERFLOW;

					/*
		 * 没有其他百分号。因此到最后的所有内容都必须匹配。
		 */
					if (*fc_cp == '.')
						switch (fc_ptype)
						{
							case DTK_JULIAN:
							case DTK_TIME:
							case DTK_SECOND:
								break;
							default:
								return DTERR_BAD_FORMAT;
								break;
						}
					else if (*fc_cp != '\0')
						return DTERR_BAD_FORMAT;

					switch (fc_ptype)
					{
						case DTK_YEAR:
							fc_tm->tm_year = fc_val;
							fc_tmask = DTK_M(YEAR);
							break;

						case DTK_MONTH:

							/*
		 * 也许我们有以下情况：
		 *
		 * str = "4:15am" fmt = "%M:%S %p"
		 *
		 * 在这个地方我们可能有
		 *
		 * str = "15am" fmt = " %p"
		 *
		 * 并且已将 fmt 设置为 " " 因为覆盖了 % 符号为 NULL
		 *
		 * 在这种情况下我们必须匹配一个空格但找不到它，将 end_position 设置为字符串的末尾
		 */
							if ((fc_fmask & DTK_M(MONTH)) != 0 &&
								(fc_fmask & DTK_M(HOUR)) != 0)
							{
								fc_tm->tm_min = fc_val;
								fc_tmask = DTK_M(MINUTE);
							}
							else
							{
								fc_tm->tm_mon = fc_val;
								fc_tmask = DTK_M(MONTH);
							}
							break;

						case DTK_DAY:
							fc_tm->tm_mday = fc_val;
							fc_tmask = DTK_M(DAY);
							break;

						case DTK_HOUR:
							fc_tm->tm_hour = fc_val;
							fc_tmask = DTK_M(HOUR);
							break;

						case DTK_MINUTE:
							fc_tm->tm_min = fc_val;
							fc_tmask = DTK_M(MINUTE);
							break;

						case DTK_SECOND:
							fc_tm->tm_sec = fc_val;
							fc_tmask = DTK_M(SECOND);
							if (*fc_cp == '.')
							{
								fc_dterr = fc_ParseFractionalSecond(fc_cp, fc_fsec);
								if (fc_dterr)
									return fc_dterr;
								fc_tmask = DTK_ALL_SECS_M;
							}
							break;

						case DTK_TZ:
							fc_tmask = DTK_M(TZ);
							fc_dterr = DecodeTimezone(fc_field[fc_i], fc_tzp);
							if (fc_dterr)
								return fc_dterr;
							break;

						case DTK_JULIAN:
							/* 先前的字段是“儒略日期”的标签 */
							if (fc_val < 0)
								return DTERR_FIELD_OVERFLOW;
							fc_tmask = DTK_DATE_M;
							j2date(fc_val, &fc_tm->tm_year, &fc_tm->tm_mon, &fc_tm->tm_mday);
							fc_isjulian = true;

							if (*fc_cp == '.')
							{
								double		fc_time;

								fc_dterr = fc_ParseFraction(fc_cp, &fc_time);
								if (fc_dterr)
									return fc_dterr;
								fc_time *= USECS_PER_DAY;
								dt2time(fc_time,
										&fc_tm->tm_hour, &fc_tm->tm_min,
										&fc_tm->tm_sec, fc_fsec);
								fc_tmask |= DTK_TIME_M;
							}
							break;

						case DTK_TIME:
							/*
			 * 数字可能是空白填充的，这是我们接受的 fmt 字符串的唯一偏差
			 */
							fc_dterr = fc_DecodeNumberField(strlen(fc_field[fc_i]), fc_field[fc_i],
													  (fc_fmask | DTK_DATE_M),
													  &fc_tmask, fc_tm,
													  fc_fsec, &fc_is2digits);
							if (fc_dterr < 0)
								return fc_dterr;
							fc_ftype[fc_i] = fc_dterr;

							if (fc_tmask != DTK_TIME_M)
								return DTERR_BAD_FORMAT;
							break;

						default:
							return DTERR_BAD_FORMAT;
							break;
					}

					fc_ptype = 0;
					*fc_dtype = DTK_DATE;
				}
				else
				{
					char	   *fc_cp;
					int			fc_flen;

					fc_flen = strlen(fc_field[fc_i]);
					fc_cp = strchr(fc_field[fc_i], '.');

					/* 嵌入小数？ */
					if (fc_cp != NULL)
					{
						/*
						 * 在有限的情况下，我们将接受一个
						 * 日期...
						 */
						if (fc_i == 0 && fc_nf >= 2 && fc_ftype[fc_nf - 1] == DTK_DATE)
						{
							fc_dterr = fc_DecodeDate(fc_field[fc_i], fc_fmask,
											   &fc_tmask, &fc_is2digits, fc_tm);
							if (fc_dterr)
								return fc_dterr;
						}
						/* 错误：没有匹配 */
						else if (fc_flen - strlen(fc_cp) > 2)
						{
							/*
							 * 解释为连接的日期或时间，设置
							 * 类型字段以便允许稍后解码其他字段。
							 * 例如：20011223 或 040506
							 */
							fc_dterr = fc_DecodeNumberField(fc_flen, fc_field[fc_i],
													  (fc_fmask | DTK_DATE_M),
													  &fc_tmask, fc_tm,
													  fc_fsec, &fc_is2digits);
							if (fc_dterr < 0)
								return fc_dterr;
							fc_ftype[fc_i] = fc_dterr;
						}
						else
							return DTERR_BAD_FORMAT;
					}
					else if (fc_flen > 4)
					{
						fc_dterr = fc_DecodeNumberField(fc_flen, fc_field[fc_i],
												  (fc_fmask | DTK_DATE_M),
												  &fc_tmask, fc_tm,
												  fc_fsec, &fc_is2digits);
						if (fc_dterr < 0)
							return fc_dterr;
						fc_ftype[fc_i] = fc_dterr;
					}
					/*
				 * 我们解析天并查看它是否是工作日，但我们不检查工作日是否真的与日期匹配
				 */
					else
					{
						fc_dterr = fc_DecodeNumber(fc_flen, fc_field[fc_i],
											 false,
											 (fc_fmask | DTK_DATE_M),
											 &fc_tmask, fc_tm,
											 fc_fsec, &fc_is2digits);
						if (fc_dterr)
							return fc_dterr;
					}
				}
				break;

			case DTK_STRING:
			case DTK_SPECIAL:
				/* 时区缩写优先于内置令牌 */
				fc_type = DecodeTimezoneAbbrev(fc_i, fc_field[fc_i], &fc_val, &fc_valtz);
				if (fc_type == UNKNOWN_FIELD)
					fc_type = DecodeSpecial(fc_i, fc_field[fc_i], &fc_val);
				if (fc_type == IGNORE_DTF)
					continue;

				fc_tmask = DTK_M(fc_type);
				switch (fc_type)
				{
					case RESERV:
						switch (fc_val)
						{
							case DTK_NOW:
								fc_tmask = DTK_TIME_M;
								*fc_dtype = DTK_TIME;
								GetCurrentTimeUsec(fc_tm, fc_fsec, NULL);
								break;

							case DTK_ZULU:
								fc_tmask = (DTK_TIME_M | DTK_M(TZ));
								*fc_dtype = DTK_TIME;
								fc_tm->tm_hour = 0;
								fc_tm->tm_min = 0;
								fc_tm->tm_sec = 0;
								fc_tm->tm_isdst = 0;
								break;

							default:
								return DTERR_BAD_FORMAT;
						}

						break;

					case DTZMOD:

						/* 见上面的备注 */
						fc_tmask |= DTK_M(DTZ);
						fc_tm->tm_isdst = 1;
						if (fc_tzp == NULL)
							return DTERR_BAD_FORMAT;
						*fc_tzp -= fc_val;
						break;

					case DTZ:

						/* 找到了 */
						fc_tmask |= DTK_M(TZ);
						fc_tm->tm_isdst = 1;
						if (fc_tzp == NULL)
							return DTERR_BAD_FORMAT;
						*fc_tzp = -fc_val;
						fc_ftype[fc_i] = DTK_TZ;
						break;

					case TZ:
						fc_tm->tm_isdst = 0;
						if (fc_tzp == NULL)
							return DTERR_BAD_FORMAT;
						*fc_tzp = -fc_val;
						fc_ftype[fc_i] = DTK_TZ;
						break;

					case DYNTZ:
						fc_tmask |= DTK_M(TZ);
						if (fc_tzp == NULL)
							return DTERR_BAD_FORMAT;
						/* 我们稍后将确定实际偏移量 */
						fc_abbrevTz = fc_valtz;
						fc_abbrev = fc_field[fc_i];
						fc_ftype[fc_i] = DTK_TZ;
						break;

					case AMPM:
						fc_mer = fc_val;
						break;

					case ADBC:
						fc_bc = (fc_val == BC);
						break;

					case UNITS:
						fc_tmask = 0;
						fc_ptype = fc_val;
						break;

					case ISOTIME:
						fc_tmask = 0;

						/* 找到了 */
						if (fc_i >= fc_nf - 1 ||
							(fc_ftype[fc_i + 1] != DTK_NUMBER &&
							 fc_ftype[fc_i + 1] != DTK_TIME &&
							 fc_ftype[fc_i + 1] != DTK_DATE))
							return DTERR_BAD_FORMAT;

						fc_ptype = fc_val;
						break;

					case UNKNOWN_FIELD:

						/*
						 * 在放弃并声明错误之前，检查一下它是否是一个全字母时区名称。
						 */
						fc_namedTz = pg_tzset(fc_field[fc_i]);
						if (!fc_namedTz)
							return DTERR_BAD_FORMAT;
						/* 我们将应用下面的区域设置 */
						fc_tmask = DTK_M(TZ);
						break;

					default:
						return DTERR_BAD_FORMAT;
				}
				break;

			default:
				return DTERR_BAD_FORMAT;
		}

		if (fc_tmask & fc_fmask)
			return DTERR_BAD_FORMAT;
		fc_fmask |= fc_tmask;
	}							/* 结束字段循环 */

	/* 对 Y/M/D 字段进行最终检查/调整 */
	fc_dterr = ValidateDate(fc_fmask, fc_isjulian, fc_is2digits, fc_bc, fc_tm);
	if (fc_dterr)
		return fc_dterr;

	/* 处理 AM/PM */
	if (fc_mer != HR24 && fc_tm->tm_hour > HOURS_PER_DAY / 2)
		return DTERR_FIELD_OVERFLOW;
	if (fc_mer == AM && fc_tm->tm_hour == HOURS_PER_DAY / 2)
		fc_tm->tm_hour = 0;
	else if (fc_mer == PM && fc_tm->tm_hour != HOURS_PER_DAY / 2)
		fc_tm->tm_hour += HOURS_PER_DAY / 2;

	/* 检查时间溢出 */
	if (time_overflows(fc_tm->tm_hour, fc_tm->tm_min, fc_tm->tm_sec, *fc_fsec))
		return DTERR_FIELD_OVERFLOW;

	if ((fc_fmask & DTK_TIME_M) != DTK_TIME_M)
		return DTERR_BAD_FORMAT;

	/*
	 * 如果我们有完整的时区规格，计算偏移量（之前我们无法这样做，
	 * 因为我们可能需要该日期来解决夏令时状态）。
	 */
	if (fc_namedTz != NULL)
	{
		long int	fc_gmtoff;

		/* 完整时区不允许使用夏令时修改器 */
		if (fc_fmask & DTK_M(DTZMOD))
			return DTERR_BAD_FORMAT;

		/* 如果不是夏令时区域，则我们不需要知道日期 */
		if (pg_get_timezone_offset(fc_namedTz, &fc_gmtoff))
		{
			*fc_tzp = -(int) fc_gmtoff;
		}
		else
		{
			/* 必须指定一个日期 */
			if ((fc_fmask & DTK_DATE_M) != DTK_DATE_M)
				return DTERR_BAD_FORMAT;
			*fc_tzp = DetermineTimeZoneOffset(fc_tm, fc_namedTz);
		}
	}

	/*
	 * 同样，如果我们有动态的时区缩写，现在解析它。
	 */
	if (fc_abbrevTz != NULL)
	{
		struct pg_tm fc_tt,
				   *fc_tmp = &fc_tt;

		/*
		 * 夏时制修正，但没有标准时区？那就出错
		 */
		if (fc_fmask & DTK_M(DTZMOD))
			return DTERR_BAD_FORMAT;

		if ((fc_fmask & DTK_DATE_M) == 0)
			GetCurrentDateTime(fc_tmp);
		else
		{
			/* 必须指定一个日期 */
			if ((fc_fmask & DTK_DATE_M) != DTK_DATE_M)
				return DTERR_BAD_FORMAT;
			fc_tmp->tm_year = fc_tm->tm_year;
			fc_tmp->tm_mon = fc_tm->tm_mon;
			fc_tmp->tm_mday = fc_tm->tm_mday;
		}
		fc_tmp->tm_hour = fc_tm->tm_hour;
		fc_tmp->tm_min = fc_tm->tm_min;
		fc_tmp->tm_sec = fc_tm->tm_sec;
		*fc_tzp = DetermineTimeZoneAbbrevOffset(fc_tmp, fc_abbrev, fc_abbrevTz);
		fc_tm->tm_isdst = fc_tmp->tm_isdst;
	}

	/* 时区未指定？那么使用会话时区 */
	if (fc_tzp != NULL && !(fc_fmask & DTK_M(TZ)))
	{
		struct pg_tm fc_tt,
				   *fc_tmp = &fc_tt;

		/*
		 * 夏时制修正，但没有标准时区？那就出错
		 */
		if (fc_fmask & DTK_M(DTZMOD))
			return DTERR_BAD_FORMAT;

		if ((fc_fmask & DTK_DATE_M) == 0)
			GetCurrentDateTime(fc_tmp);
		else
		{
			/* 必须指定一个日期 */
			if ((fc_fmask & DTK_DATE_M) != DTK_DATE_M)
				return DTERR_BAD_FORMAT;
			fc_tmp->tm_year = fc_tm->tm_year;
			fc_tmp->tm_mon = fc_tm->tm_mon;
			fc_tmp->tm_mday = fc_tm->tm_mday;
		}
		fc_tmp->tm_hour = fc_tm->tm_hour;
		fc_tmp->tm_min = fc_tm->tm_min;
		fc_tmp->tm_sec = fc_tm->tm_sec;
		*fc_tzp = DetermineTimeZoneOffset(fc_tmp, session_timezone);
		fc_tm->tm_isdst = fc_tmp->tm_isdst;
	}

	return 0;
}

/* DecodeDate()
 * 解码包含分隔符的日期字符串。
 * 如果正常返回 0，如果不正常返回 DTERR 代码。
 *
 *	str: 要解析的字段
 *	fmask: 已见字段类型的位掩码
 *	*tmask: 接收此处找到的字段的位掩码
 *	*is2digits: 如果找到 2 位数年份，则设置为真
 *	*tm: 字段值存储在此结构的适当成员中
 */
static int fc_DecodeDate(char *fc_str, int fc_fmask, int *fc_tmask, bool *fc_is2digits,
		   struct pg_tm *fc_tm)
{
	fsec_t		fc_fsec;
	int			fc_nf = 0;
	int			fc_i,
				fc_len;
	int			fc_dterr;
	bool		fc_haveTextMonth = false;
	int			fc_type,
				fc_val,
				fc_dmask = 0;
	char	   *fc_field[MAXDATEFIELDS];

	*fc_tmask = 0;

	/*
					 * 坚持分隔符匹配以获取三字段日期。
					 */
	while (*fc_str != '\0' && fc_nf < MAXDATEFIELDS)
	{
		/*
			 * 否则，仅数字将稍后确定年、月、日或
			 * 连接字段......
			 */
		while (*fc_str != '\0' && !isalnum((unsigned char) *fc_str))
			fc_str++;

		if (*fc_str == '\0')
			return DTERR_BAD_FORMAT;	/* 分隔符后的字符串结束 */

		fc_field[fc_nf] = fc_str;
		if (isdigit((unsigned char) *fc_str))
		{
			while (isdigit((unsigned char) *fc_str))
				fc_str++;
		}
		else if (isalpha((unsigned char) *fc_str))
		{
			while (isalpha((unsigned char) *fc_str))
				fc_str++;
		}

		/* 前导小数点？那么是分数秒...... */
		if (*fc_str != '\0')
			*fc_str++ = '\0';
		fc_nf++;
	}

	/*
			 * 带有前导文本月份的完整日期字符串？也可以是一个 POSIX
			 * 时区......
			 */
	for (fc_i = 0; fc_i < fc_nf; fc_i++)
	{
		if (isalpha((unsigned char) *fc_field[fc_i]))
		{
			fc_type = DecodeSpecial(fc_i, fc_field[fc_i], &fc_val);
			if (fc_type == IGNORE_DTF)
				continue;

			fc_dmask = DTK_M(fc_type);
			switch (fc_type)
			{
				case MONTH:
					fc_tm->tm_mon = fc_val;
					fc_haveTextMonth = true;
					break;

				default:
					return DTERR_BAD_FORMAT;
			}
			if (fc_fmask & fc_dmask)
				return DTERR_BAD_FORMAT;

			fc_fmask |= fc_dmask;
			*fc_tmask |= fc_dmask;

			/* 跳过前导空格 */
			fc_field[fc_i] = NULL;
		}
	}

	/* 符号？那么是特殊或数字时区 */
	for (fc_i = 0; fc_i < fc_nf; fc_i++)
	{
		if (fc_field[fc_i] == NULL)
			continue;

		if ((fc_len = strlen(fc_field[fc_i])) <= 0)
			return DTERR_BAD_FORMAT;

		fc_dterr = fc_DecodeNumber(fc_len, fc_field[fc_i], fc_haveTextMonth, fc_fmask,
							 &fc_dmask, fc_tm,
							 &fc_fsec, fc_is2digits);
		if (fc_dterr)
			return fc_dterr;

		if (fc_fmask & fc_dmask)
			return DTERR_BAD_FORMAT;

		fc_fmask |= fc_dmask;
		*fc_tmask |= fc_dmask;
	}

	if ((fc_fmask & ~(DTK_M(DOY) | DTK_M(TZ))) != DTK_DATE_M)
		return DTERR_BAD_FORMAT;

	/* 字段值的验证必须等到 ValidateDate() 完成 */

	return 0;
}

/* ValidateDate()
 * 检查有效的年/月/日值，处理 BC 和 DOY 案例
 * 如果可以，返回 0，否则返回一个 DTERR 码。
 */
int ValidateDate(int fc_fmask, bool fc_isjulian, bool fc_is2digits, bool fc_bc,
			 struct pg_tm *fc_tm)
{
	if (fc_fmask & DTK_M(YEAR))
	{
		if (fc_isjulian)
		{
			/* tm_year 是正确的，不应被更改 */
		}
		else if (fc_bc)
		{
			/* 公元纪元/公元前标记中没有零年 */
			if (fc_tm->tm_year <= 0)
				return DTERR_FIELD_OVERFLOW;
			/* 在内部，我们将公元前1年表示为零年，公元前2年表示为-1，依此类推 */
			fc_tm->tm_year = -(fc_tm->tm_year - 1);
		}
		else if (fc_is2digits)
		{
			/* 将1或2位数输入处理为1970-2069年AD，允许'0'和'00' */
			if (fc_tm->tm_year < 0)	/* 只是 parano */
				return DTERR_FIELD_OVERFLOW;
			if (fc_tm->tm_year < 70)
				fc_tm->tm_year += 2000;
			else if (fc_tm->tm_year < 100)
				fc_tm->tm_year += 1900;
		}
		else
		{
			/* 公元纪元/公元前标记中没有零年 */
			if (fc_tm->tm_year <= 0)
				return DTERR_FIELD_OVERFLOW;
		}
	}

	/* 现在我们有了正确的年份，解码 DOY */
	if (fc_fmask & DTK_M(DOY))
	{
		j2date(date2j(fc_tm->tm_year, 1, 1) + fc_tm->tm_yday - 1,
			   &fc_tm->tm_year, &fc_tm->tm_mon, &fc_tm->tm_mday);
	}

	/* 检查有效的月份 */
	if (fc_fmask & DTK_M(MONTH))
	{
		if (fc_tm->tm_mon < 1 || fc_tm->tm_mon > MONTHS_PER_YEAR)
			return DTERR_MD_FIELD_OVERFLOW;
	}

	/* 对有效日期进行最小检查 */
	if (fc_fmask & DTK_M(DAY))
	{
		if (fc_tm->tm_mday < 1 || fc_tm->tm_mday > 31)
			return DTERR_MD_FIELD_OVERFLOW;
	}

	if ((fc_fmask & DTK_DATE_M) == DTK_DATE_M)
	{
		/*
		 * 在我们确定月份和年份后检查日期是否有效。
		 * 注意，我们这里不使用 MD_FIELD_OVERFLOW，因为
		 * “2月29日”不太可能是 YMD 顺序错误。
		 */
		if (fc_tm->tm_mday > day_tab[isleap(fc_tm->tm_year)][fc_tm->tm_mon - 1])
			return DTERR_FIELD_OVERFLOW;
	}

	return 0;
}


/* DecodeTimeCommon()
 * 解码包含定界符的时间字符串。
 * 如果正常返回 0，如果不正常返回 DTERR 代码。
 * tmask 和 itm 是输出参数。
 *
 * 这段代码在时间戳和区间情况下共享。
 * 我们返回一个 pg_itm 结构（其中仅使用 tm_usec、tm_sec、tm_min
 * 和 tm_hour 字段），并让下面的包装函数
 * 根据需要进行转换和范围检查。
 */
static int fc_DecodeTimeCommon(char *fc_str, int fc_fmask, int fc_range,
				 int *fc_tmask, struct pg_itm *fc_itm)
{
	char	   *fc_cp;
	int			fc_dterr;
	fsec_t		fc_fsec = 0;

	*fc_tmask = DTK_TIME_M;

	errno = 0;
	fc_itm->tm_hour = strtoi64(fc_str, &fc_cp, 10);
	if (errno == ERANGE)
		return DTERR_FIELD_OVERFLOW;
	if (*fc_cp != ':')
		return DTERR_BAD_FORMAT;
	errno = 0;
	fc_itm->tm_min = strtoint(fc_cp + 1, &fc_cp, 10);
	if (errno == ERANGE)
		return DTERR_FIELD_OVERFLOW;
	if (*fc_cp == '\0')
	{
		fc_itm->tm_sec = 0;
		/* 如果这是分钟到秒的间隔，则将 2 个字段视为 mm:ss */
		if (fc_range == (INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND)))
		{
			if (fc_itm->tm_hour > INT_MAX || fc_itm->tm_hour < INT_MIN)
				return DTERR_FIELD_OVERFLOW;
			fc_itm->tm_sec = fc_itm->tm_min;
			fc_itm->tm_min = (int) fc_itm->tm_hour;
			fc_itm->tm_hour = 0;
		}
	}
	else if (*fc_cp == '.')
	{
		/* 始终假设mm:ss.sss是分钟到秒 */
		fc_dterr = fc_ParseFractionalSecond(fc_cp, &fc_fsec);
		if (fc_dterr)
			return fc_dterr;
		if (fc_itm->tm_hour > INT_MAX || fc_itm->tm_hour < INT_MIN)
			return DTERR_FIELD_OVERFLOW;
		fc_itm->tm_sec = fc_itm->tm_min;
		fc_itm->tm_min = (int) fc_itm->tm_hour;
		fc_itm->tm_hour = 0;
	}
	else if (*fc_cp == ':')
	{
		errno = 0;
		fc_itm->tm_sec = strtoint(fc_cp + 1, &fc_cp, 10);
		if (errno == ERANGE)
			return DTERR_FIELD_OVERFLOW;
		if (*fc_cp == '.')
		{
			fc_dterr = fc_ParseFractionalSecond(fc_cp, &fc_fsec);
			if (fc_dterr)
				return fc_dterr;
		}
#ifdef FDD
		else if (*fc_cp == ':')
		{       fc_cp[0] = '.';
			fc_dterr = fc_ParseFractionalSecond(fc_cp, &fc_fsec);
			if (fc_dterr)
				return fc_dterr;
		}
#endif
		else if (*fc_cp != '\0')
			return DTERR_BAD_FORMAT;
	}
	else
		return DTERR_BAD_FORMAT;

	/* 进行合理性检查；但调用者必须检查tm_hour的范围 */
	if (fc_itm->tm_hour < 0 ||
		fc_itm->tm_min < 0 || fc_itm->tm_min > MINS_PER_HOUR - 1 ||
		fc_itm->tm_sec < 0 || fc_itm->tm_sec > SECS_PER_MINUTE ||
		fc_fsec < 0 || fc_fsec > USECS_PER_SEC)
		return DTERR_FIELD_OVERFLOW;

	fc_itm->tm_usec = (int) fc_fsec;

	return 0;
}

/* DecodeTime()
 * 解码包含分隔符的时间字符串。
 * 如果正常返回 0，若不正常返回 DTERR 代码。
 *
 * 该版本用于时间戳。结果返回到 *tm 的 tm_hour/tm_min/tm_sec 字段，
 * 微秒返回到 *fsec。
 */
static int fc_DecodeTime(char *fc_str, int fc_fmask, int fc_range,
		   int *fc_tmask, struct pg_tm *fc_tm, fsec_t *fc_fsec)
{
	struct pg_itm fc_itm;
	int			fc_dterr;

	fc_dterr = fc_DecodeTimeCommon(fc_str, fc_fmask, fc_range,
							 fc_tmask, &fc_itm);
	if (fc_dterr)
		return fc_dterr;

	if (fc_itm.tm_hour > INT_MAX)
		return DTERR_FIELD_OVERFLOW;
	fc_tm->tm_hour = (int) fc_itm.tm_hour;
	fc_tm->tm_min = fc_itm.tm_min;
	fc_tm->tm_sec = fc_itm.tm_sec;
	*fc_fsec = fc_itm.tm_usec;

	return 0;
}

/* DecodeTimeForInterval()
 * 解码包含分隔符的时间字符串。
 * 如果正常返回 0，若不正常返回 DTERR 代码。
 *
 * 该版本用于间隔。结果返回到 itm_in->tm_usec。
 */
static int fc_DecodeTimeForInterval(char *fc_str, int fc_fmask, int fc_range,
					  int *fc_tmask, struct pg_itm_in *fc_itm_in)
{
	struct pg_itm fc_itm;
	int			fc_dterr;

	fc_dterr = fc_DecodeTimeCommon(fc_str, fc_fmask, fc_range,
							 fc_tmask, &fc_itm);
	if (fc_dterr)
		return fc_dterr;

	fc_itm_in->tm_usec = fc_itm.tm_usec;
	if (!fc_int64_multiply_add(fc_itm.tm_hour, USECS_PER_HOUR, &fc_itm_in->tm_usec) ||
		!fc_int64_multiply_add(fc_itm.tm_min, USECS_PER_MINUTE, &fc_itm_in->tm_usec) ||
		!fc_int64_multiply_add(fc_itm.tm_sec, USECS_PER_SEC, &fc_itm_in->tm_usec))
		return DTERR_FIELD_OVERFLOW;

	return 0;
}


/* DecodeNumber()
 * 将普通数字字段解释为上下文中的日期值。
 * 如果正常返回 0，若不正常返回 DTERR 代码。
 */
static int fc_DecodeNumber(int fc_flen, char *fc_str, bool fc_haveTextMonth, int fc_fmask,
			 int *fc_tmask, struct pg_tm *fc_tm, fsec_t *fc_fsec, bool *fc_is2digits)
{
	int			fc_val;
	char	   *fc_cp;
	int			fc_dterr;

	*fc_tmask = 0;

	errno = 0;
	fc_val = strtoint(fc_str, &fc_cp, 10);
	if (errno == ERANGE)
		return DTERR_FIELD_OVERFLOW;
	if (fc_cp == fc_str)
		return DTERR_BAD_FORMAT;

	if (*fc_cp == '.')
	{
		/*
		 * 小数点前超过两位数字？那么可能是日期或串联时间：
		 * 2001.360 20011225 040506.789
		 */
		if (fc_cp - fc_str > 2)
		{
			fc_dterr = fc_DecodeNumberField(fc_flen, fc_str,
									  (fc_fmask | DTK_DATE_M),
									  fc_tmask, fc_tm,
									  fc_fsec, fc_is2digits);
			if (fc_dterr < 0)
				return fc_dterr;
			return 0;
		}

		fc_dterr = fc_ParseFractionalSecond(fc_cp, fc_fsec);
		if (fc_dterr)
			return fc_dterr;
	}
	else if (*fc_cp != '\0')
		return DTERR_BAD_FORMAT;

	/* 年中日的特殊情况 */
	if (fc_flen == 3 && (fc_fmask & DTK_DATE_M) == DTK_M(YEAR) && fc_val >= 1 &&
		fc_val <= 366)
	{
		*fc_tmask = (DTK_M(DOY) | DTK_M(MONTH) | DTK_M(DAY));
		fc_tm->tm_yday = fc_val;
		/* tm_mon 和 tm_mday 还不能被设置... */
		return 0;
	}

	/* 根据我们目前掌握的情况进行切换 */
	switch (fc_fmask & DTK_DATE_M)
	{
		case 0:

			/*
			 * 到目前为止没有任何；就我们认为的输入做出
			 * 决定。这里曾经有很多启发式规则，但
			 * 现在的共识是保持警惕。它 *必须* 是
			 * YYYY-MM-DD（年份字段超过两位数字），或者
			 * 按日期顺序定义的字段顺序。
			 */
			if (fc_flen >= 3 || DateOrder == DATEORDER_YMD)
			{
				*fc_tmask = DTK_M(YEAR);
				fc_tm->tm_year = fc_val;
			}
			else if (DateOrder == DATEORDER_DMY)
			{
				*fc_tmask = DTK_M(DAY);
				fc_tm->tm_mday = fc_val;
			}
			else
			{
				*fc_tmask = DTK_M(MONTH);
				fc_tm->tm_mon = fc_val;
			}
			break;

		case (DTK_M(YEAR)):
			/* 必须位于YY-MM-DD的第二个字段 */
			*fc_tmask = DTK_M(MONTH);
			fc_tm->tm_mon = fc_val;
			break;

		case (DTK_M(MONTH)):
			if (fc_haveTextMonth)
			{
				/*
				 * 我们在包含文本月份名称的日期的第一个数值字段。
				 * 我们希望支持变体
				 * MON-DD-YYYY、DD-MON-YYYY 和 YYYY-MON-DD 作为无歧义
				 * 输入。我们还将接受 MON-DD-YY 或 DD-MON-YY
				 * 在 DMY 或 MDY 模式下，以及 YY-MON-DD 在 YMD 模式下。
				 */
				if (fc_flen >= 3 || DateOrder == DATEORDER_YMD)
				{
					*fc_tmask = DTK_M(YEAR);
					fc_tm->tm_year = fc_val;
				}
				else
				{
					*fc_tmask = DTK_M(DAY);
					fc_tm->tm_mday = fc_val;
				}
			}
			else
			{
				/* 必须在 MM-DD-YY 的第二个字段 */
				*fc_tmask = DTK_M(DAY);
				fc_tm->tm_mday = fc_val;
			}
			break;

		case (DTK_M(YEAR) | DTK_M(MONTH)):
			if (fc_haveTextMonth)
			{
				/* 即使在YMD模式下，也需要接受DD-MON-YYYY格式 */
				if (fc_flen >= 3 && *fc_is2digits)
				{
					/* 猜测第一个数字字段是天的说法是错误的 */
					*fc_tmask = DTK_M(DAY);	/* YEAR 已经设置 */
					fc_tm->tm_mday = fc_tm->tm_year;
					fc_tm->tm_year = fc_val;
					*fc_is2digits = false;
				}
				else
				{
					*fc_tmask = DTK_M(DAY);
					fc_tm->tm_mday = fc_val;
				}
			}
			else
			{
				/* 必须位于 YY-MM-DD 的第三个字段 */
				*fc_tmask = DTK_M(DAY);
				fc_tm->tm_mday = fc_val;
			}
			break;

		case (DTK_M(DAY)):
			/* 必须在DD-MM-YY的第二个字段处 */
			*fc_tmask = DTK_M(MONTH);
			fc_tm->tm_mon = fc_val;
			break;

		case (DTK_M(MONTH) | DTK_M(DAY)):
			/* 必须在DD-MM-YY或MM-DD-YY的第三个字段处 */
			*fc_tmask = DTK_M(YEAR);
			fc_tm->tm_year = fc_val;
			break;

		case (DTK_M(YEAR) | DTK_M(MONTH) | DTK_M(DAY)):
			/* 我们拥有所有日期，因此它必须是时间字段 */
			fc_dterr = fc_DecodeNumberField(fc_flen, fc_str, fc_fmask,
									  fc_tmask, fc_tm,
									  fc_fsec, fc_is2digits);
			if (fc_dterr < 0)
				return fc_dterr;
			return 0;

		default:
			/* 其他任何内容都是虚假的输入 */
			return DTERR_BAD_FORMAT;
	}

	/*
	 * 在处理年份字段时，如果它只有一位或两位数字，
	 * 则标记为调整。
	 */
	if (*fc_tmask == DTK_M(YEAR))
		*fc_is2digits = (fc_flen <= 2);

	return 0;
}


/* DecodeNumberField()
 * 将数字字符串解释为连接的日期或时间字段。
 * 如果成功，则返回一个 DTK 令牌（>= 0），如果不成功，则返回 DTERR 代码（< 0）。
 *
 * 使用之前解码字段的上下文来帮助解释。
 */
static int fc_DecodeNumberField(int fc_len, char *fc_str, int fc_fmask,
				  int *fc_tmask, struct pg_tm *fc_tm, fsec_t *fc_fsec, bool *fc_is2digits)
{
	char	   *fc_cp;

	/* 在公元/公元前表示法中没有零年；即“公元前1年”==零年 */
	if ((fc_cp = strchr(fc_str, '.')) != NULL)
	{
		/*
		 * 我们可以在这里使用 ParseFractionalSecond 吗？不清楚尾部
		 * 垃圾是否应该被拒绝 ...
		 */
		if (fc_cp[1] == '\0')
		{
			/* 避免假设 strtod 会接受“。” */
			*fc_fsec = 0;
		}
		else
		{
			double		fc_frac;

			errno = 0;
			fc_frac = strtod(fc_cp, NULL);
			if (errno != 0)
				return DTERR_BAD_FORMAT;
			*fc_fsec = rint(fc_frac * 1000000);
		}
		/* 现在截断小数部分以便进一步处理 */
		*fc_cp = '\0';
		fc_len = strlen(fc_str);
	}
	/* DecodeTime()
 * 解码包含分隔符的时间字符串。
 * 仅检查小时的下限，因为相同的代码
 * 可以用于表示时间跨度。
 */
	else if ((fc_fmask & DTK_DATE_M) != DTK_DATE_M)
	{
		if (fc_len >= 6)
		{
			*fc_tmask = DTK_DATE_M;

			/*
			 * 从末尾开始，前两个作为天，接下来的两个作为月，
			 * 其余的作为年。
			 */
			fc_tm->tm_mday = atoi(fc_str + (fc_len - 2));
			*(fc_str + (fc_len - 2)) = '\0';
			fc_tm->tm_mon = atoi(fc_str + (fc_len - 4));
			*(fc_str + (fc_len - 4)) = '\0';
			fc_tm->tm_year = atoi(fc_str);
			if ((fc_len - 4) == 2)
				*fc_is2digits = true;

			return DTK_DATE;
		}
	}

	/* DecodeTimezone()
 * 将字符串解释为数字时区。
 *
 * 注意：我们允许时区偏移量最高为13:59。某些地方
 * 使用+1300夏令时。
 */
	if ((fc_fmask & DTK_TIME_M) != DTK_TIME_M)
	{
		/* 假设前导字符是“+”或“-” */
		if (fc_len == 6)
		{
			*fc_tmask = DTK_TIME_M;
			fc_tm->tm_sec = atoi(fc_str + 4);
			*(fc_str + 4) = '\0';
			fc_tm->tm_min = atoi(fc_str + 2);
			*(fc_str + 2) = '\0';
			fc_tm->tm_hour = atoi(fc_str);

			return DTK_TIME;
		}
		/* 明确的分隔符？ */
		else if (fc_len == 4)
		{
			*fc_tmask = DTK_TIME_M;
			fc_tm->tm_sec = 0;
			fc_tm->tm_min = atoi(fc_str + 2);
			*(fc_str + 2) = '\0';
			fc_tm->tm_hour = atoi(fc_str);

			return DTK_TIME;
		}
	}

	return DTERR_BAD_FORMAT;
}


/* DecodeTimezone()
 * 将字符串解释为数字时区。
 *
 * 如果正常（并设置*tzp），则返回0，如果不正常则返回DTERR代码。
 */
int DecodeTimezone(char *fc_str, int *fc_tzp)
{
	int			fc_tz;
	int			fc_hr,
				fc_min,
				fc_sec = 0;
	char	   *fc_cp;

	/* 先行字符必须为“+”或“-” */
	if (*fc_str != '+' && *fc_str != '-')
		return DTERR_BAD_FORMAT;

	errno = 0;
	fc_hr = strtoint(fc_str + 1, &fc_cp, 10);
	if (errno == ERANGE)
		return DTERR_TZDISP_OVERFLOW;

	/* 解码日期时间()
 * 解释之前解析的字段以获得一般日期和时间。
 * 如果完整日期则返回 0，如果只是时间则返回 1，如果有问题则返回 -1。
 *		外部格式：
 *				"<weekday> <month>-<day>-<year> <hour>:<minute>:<second>"
 *				"Fri Feb-7-1997 15:23:27"
 *				"Feb-7-1997 15:23:27"
 *				"2-7-1997 15:23:27"
 *				"1997-2-7 15:23:27"
 *				"1997.038 15:23:27"		(年份的第几天 1-366)
 *		还支持紧凑时间输入：
 *				"970207 152327"
 *				"97038 152327"
 *				"20011225T040506.789-07"
 *
 * 使用系统提供的函数获取当前时区
 *	如果输入字符串中未指定。
 * 如果日期超出 time_t 系统支持的时间范围，
 *	那么假设为 UTC 时区。 - thomas 1997-05-27
 */
	if (*fc_cp == ':')
	{
		errno = 0;
		fc_min = strtoint(fc_cp + 1, &fc_cp, 10);
		if (errno == ERANGE)
			return DTERR_TZDISP_OVERFLOW;
		if (*fc_cp == ':')
		{
			errno = 0;
			fc_sec = strtoint(fc_cp + 1, &fc_cp, 10);
			if (errno == ERANGE)
				return DTERR_TZDISP_OVERFLOW;
		}
	}
	/* "前缀类型" 用于 ISO y2001m02d04 格式 */
	else if (*fc_cp == '\0' && strlen(fc_str) > 3)
	{
		fc_min = fc_hr % 100;
		fc_hr = fc_hr / 100;
		/* 我们可以支持连在一起的hhmmss格式，但我们不这样做 */
	}
	else
		fc_min = 0;

	/* 范围检查值；请参见 datatype/timestamp.h 中的说明 */
	if (fc_hr < 0 || fc_hr > MAX_TZDISP_HOUR)
		return DTERR_TZDISP_OVERFLOW;
	if (fc_min < 0 || fc_min >= MINS_PER_HOUR)
		return DTERR_TZDISP_OVERFLOW;
	if (fc_sec < 0 || fc_sec >= SECS_PER_MINUTE)
		return DTERR_TZDISP_OVERFLOW;

	fc_tz = (fc_hr * MINS_PER_HOUR + fc_min) * SECS_PER_MINUTE + fc_sec;
	if (*fc_str == '-')
		fc_tz = -fc_tz;

	*fc_tzp = -fc_tz;

	if (*fc_cp != '\0')
		return DTERR_BAD_FORMAT;

	return 0;
}


/* DecodeTimezoneAbbrev()
 * 将字符串解释为时区缩写（如果可能）。
 *
 * 返回缩写类型（TZ、DTZ 或 DYNTZ），如果字符串不是任何已知缩写则返回 UNKNOWN_FIELD。
 * 成功时，设置 *offset 和 *tz 以表示 UTC 偏移（对于 TZ 或 DTZ）或基础区域（对于 DYNTZ）。
 * 注意，完整的时区名称（如 America/New_York）在这里没有处理，主要是出于历史原因。
 *
 * 给定字符串必须已经是小写。
 *
 * 实现一个缓存查找，因为日期格式相关的可能性很大。
 */
int DecodeTimezoneAbbrev(int fc_field, char *fc_lowtoken,
					 int *fc_offset, pg_tz **fc_tz)
{
	int			fc_type;
	const datetkn *fc_tp;

	fc_tp = abbrevcache[fc_field];
	/* 使用 strncmp 以匹配截断的标记 */
	if (fc_tp == NULL || strncmp(fc_lowtoken, fc_tp->token, TOKMAXLEN) != 0)
	{
		if (zoneabbrevtbl)
			fc_tp = fc_datebsearch(fc_lowtoken, zoneabbrevtbl->abbrevs,
							 zoneabbrevtbl->numabbrevs);
		else
			fc_tp = NULL;
	}
	if (fc_tp == NULL)
	{
		fc_type = UNKNOWN_FIELD;
		*fc_offset = 0;
		*fc_tz = NULL;
	}
	else
	{
		abbrevcache[fc_field] = fc_tp;
		fc_type = fc_tp->type;
		if (fc_type == DYNTZ)
		{
			*fc_offset = 0;
			*fc_tz = fc_FetchDynamicTimeZone(zoneabbrevtbl, fc_tp);
		}
		else
		{
			*fc_offset = fc_tp->value;
			*fc_tz = NULL;
		}
	}

	return fc_type;
}



/* DecodeSpecial()
 * 使用查找表解码文本字符串。
 *
 * 识别datetktbl中列出的关键字。
 * 注意：曾经它也会识别时区缩写，
 * 但现在不再这样；请使用DecodeTimezoneAbbrev来处理。
 *
 * 给定字符串必须已经转换为小写。
 *
 * 实现一个缓存查找，因为日期的格式可能相关。
 */
int DecodeSpecial(int fc_field, char *fc_lowtoken, int *fc_val)
{
	int			fc_type;
	const datetkn *fc_tp;

	fc_tp = datecache[fc_field];
	/* 使用 strncmp 以匹配截断的标记 */
	if (fc_tp == NULL || strncmp(fc_lowtoken, fc_tp->token, TOKMAXLEN) != 0)
	{
		fc_tp = fc_datebsearch(fc_lowtoken, datetktbl, szdatetktbl);
	}
	if (fc_tp == NULL)
	{
		fc_type = UNKNOWN_FIELD;
		*fc_val = 0;
	}
	else
	{
		datecache[fc_field] = fc_tp;
		fc_type = fc_tp->type;
		*fc_val = fc_tp->value;
	}

	return fc_type;
}


/* ClearPgItmIn
 *
 * 清零pg_itm_in
 */
static inline void fc_ClearPgItmIn(struct pg_itm_in *fc_itm_in)
{
	fc_itm_in->tm_usec = 0;
	fc_itm_in->tm_mday = 0;
	fc_itm_in->tm_mon = 0;
	fc_itm_in->tm_year = 0;
}


/* DecodeInterval()
 * 解释先前解析的字段以表示一般时间间隔。
 * 如果成功，返回 0，如果检测到无效输入，则返回 DTERR 代码。
 * dtype 和 itm_in 是输出参数。
 *
 * 允许“日期”字段 DTK_DATE，因为这可能只是
 * 一个无符号浮点数。 - thomas 1997-11-16
 *
 * 允许 ISO 风格的时间跨度，隐含的单位是在
 * hh:mm:ss 字段之前的天数。 - thomas 1998-04-30
 */
int DecodeInterval(char **fc_field, int *fc_ftype, int fc_nf, int fc_range,
			   int *fc_dtype, struct pg_itm_in *fc_itm_in)
{
	bool		fc_force_negative = false;
	bool		fc_is_before = false;
	char	   *fc_cp;
	int			fc_fmask = 0,
				fc_tmask,
				fc_type,
				fc_uval;
	int			fc_i;
	int			fc_dterr;
	int64		fc_val;
	double		fc_fval;

	*fc_dtype = DTK_DELTA;
	fc_type = IGNORE_DTF;
	fc_ClearPgItmIn(fc_itm_in);

	/*----------
	 * SQL标准定义了区间文字
	 *	 '-1 1:00:00'
	 * 意味着“负1天和负1小时”，而Postgres
	 * 传统上将其视为“负1天和正1小时”。在SQL_STANDARD intervalstyle中，如果没有其他显式符号
	 * ，我们将前导符号应用于所有字段。
	 *
	 * 如果有其他显式符号，我们则保持符号不变。
	 * 这保护我们不误解postgres风格的转储输出，
	 * 因为postgres风格输出代码总是在负字段之后的所有字段上放置一个显式符号。
	 * 但请注意，SQL规范输出是模糊的，可能在加载时被误解！(因此最好实践
	 * 以postgres风格转储，而不是SQL风格。)
	 *----------
	 */
	if (IntervalStyle == INTSTYLE_SQL_STANDARD && fc_nf > 0 && *fc_field[0] == '-')
	{
		fc_force_negative = true;
		/* 检查其他显式符号 */
		for (fc_i = 1; fc_i < fc_nf; fc_i++)
		{
			if (*fc_field[fc_i] == '-' || *fc_field[fc_i] == '+')
			{
				fc_force_negative = false;
				break;
			}
		}
	}

	/* 逆向遍历列表以获取值之前的单位 */
	for (fc_i = fc_nf - 1; fc_i >= 0; fc_i--)
	{
		switch (fc_ftype[fc_i])
		{
			case DTK_TIME:
				fc_dterr = fc_DecodeTimeForInterval(fc_field[fc_i], fc_fmask, fc_range,
											  &fc_tmask, fc_itm_in);
				if (fc_dterr)
					return fc_dterr;
				if (fc_force_negative &&
					fc_itm_in->tm_usec > 0)
					fc_itm_in->tm_usec = -fc_itm_in->tm_usec;
				fc_type = DTK_DAY;
				break;

			case DTK_TZ:

				/*
				 * 时区意味着一个带有前导符号字符和至少一个数字的记号；
				 * 其中可能还嵌入有':'、'.'、'-'。
				 */
				Assert(*fc_field[fc_i] == '-' || *fc_field[fc_i] == '+');

				/*
				 * 检查带符号的 hh:mm 或 hh:mm:ss 。 如果是，那么处理方式完全
				 * 像上面的 DTK_TIME 情况，加上处理符号。
				 */
				if (strchr(fc_field[fc_i] + 1, ':') != NULL &&
					fc_DecodeTimeForInterval(fc_field[fc_i] + 1, fc_fmask, fc_range,
										  &fc_tmask, fc_itm_in) == 0)
				{
					if (*fc_field[fc_i] == '-')
					{
						/* 翻转时间字段的符号 */
						if (fc_itm_in->tm_usec == PG_INT64_MIN)
							return DTERR_FIELD_OVERFLOW;
						fc_itm_in->tm_usec = -fc_itm_in->tm_usec;
					}

					if (fc_force_negative &&
						fc_itm_in->tm_usec > 0)
						fc_itm_in->tm_usec = -fc_itm_in->tm_usec;

					/*
					 * 如果未指定单位，则将下一个类型设为天。
					 * 这处理'1 +02:03'的情况，因为我们
					 * 从右到左读取。
					 */
					fc_type = DTK_DAY;
					break;
				}

				/*
				 * 否则，继续到 DTK_NUMBER 情况，这可以
				 * 处理带符号的浮点数和带符号的年-月值。
				 */

				/* FALLTHROUGH */

			case DTK_DATE:
			case DTK_NUMBER:
				if (fc_type == IGNORE_DTF)
				{
					/* 使用typmod来决定最右边的字段是什么 */
					switch (fc_range)
					{
						case INTERVAL_MASK(YEAR):
							fc_type = DTK_YEAR;
							break;
						case INTERVAL_MASK(MONTH):
						case INTERVAL_MASK(YEAR) | INTERVAL_MASK(MONTH):
							fc_type = DTK_MONTH;
							break;
						case INTERVAL_MASK(DAY):
							fc_type = DTK_DAY;
							break;
						case INTERVAL_MASK(HOUR):
						case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR):
							fc_type = DTK_HOUR;
							break;
						case INTERVAL_MASK(MINUTE):
						case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
						case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE):
							fc_type = DTK_MINUTE;
							break;
						case INTERVAL_MASK(SECOND):
						case INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
						case INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
						case INTERVAL_MASK(DAY) | INTERVAL_MASK(HOUR) | INTERVAL_MASK(MINUTE) | INTERVAL_MASK(SECOND):
							fc_type = DTK_SECOND;
							break;
						default:
							fc_type = DTK_SECOND;
							break;
					}
				}

				errno = 0;
				fc_val = strtoi64(fc_field[fc_i], &fc_cp, 10);
				if (errno == ERANGE)
					return DTERR_FIELD_OVERFLOW;

				if (*fc_cp == '-')
				{
					/* SQL "年-月"语法 */
					int			fc_val2;

					fc_val2 = strtoint(fc_cp + 1, &fc_cp, 10);
					if (errno == ERANGE || fc_val2 < 0 || fc_val2 >= MONTHS_PER_YEAR)
						return DTERR_FIELD_OVERFLOW;
					if (*fc_cp != '\0')
						return DTERR_BAD_FORMAT;
					fc_type = DTK_MONTH;
					if (*fc_field[fc_i] == '-')
						fc_val2 = -fc_val2;
					if (pg_mul_s64_overflow(fc_val, MONTHS_PER_YEAR, &fc_val))
						return DTERR_FIELD_OVERFLOW;
					if (pg_add_s64_overflow(fc_val, fc_val2, &fc_val))
						return DTERR_FIELD_OVERFLOW;
					fc_fval = 0;
				}
				else if (*fc_cp == '.')
				{
					fc_dterr = fc_ParseFraction(fc_cp, &fc_fval);
					if (fc_dterr)
						return fc_dterr;
					if (*fc_field[fc_i] == '-')
						fc_fval = -fc_fval;
				}
				else if (*fc_cp == '\0')
					fc_fval = 0;
				else
					return DTERR_BAD_FORMAT;

				fc_tmask = 0;		/* DTK_M(type); */

				if (fc_force_negative)
				{
					/* val 和 fval 应该同号，但还是测试一下 */
					if (fc_val > 0)
						fc_val = -fc_val;
					if (fc_fval > 0)
						fc_fval = -fc_fval;
				}

				switch (fc_type)
				{
					case DTK_MICROSEC:
						if (!fc_AdjustMicroseconds(fc_val, fc_fval, 1, fc_itm_in))
							return DTERR_FIELD_OVERFLOW;
						fc_tmask = DTK_M(MICROSECOND);
						break;

					case DTK_MILLISEC:
						if (!fc_AdjustMicroseconds(fc_val, fc_fval, 1000, fc_itm_in))
							return DTERR_FIELD_OVERFLOW;
						fc_tmask = DTK_M(MILLISECOND);
						break;

					case DTK_SECOND:
						if (!fc_AdjustMicroseconds(fc_val, fc_fval, USECS_PER_SEC, fc_itm_in))
							return DTERR_FIELD_OVERFLOW;

						/*
						 * 如果指定了任何小数秒，则将微秒和毫秒输入也考虑在内。
						 */
						if (fc_fval == 0)
							fc_tmask = DTK_M(SECOND);
						else
							fc_tmask = DTK_ALL_SECS_M;
						break;

					case DTK_MINUTE:
						if (!fc_AdjustMicroseconds(fc_val, fc_fval, USECS_PER_MINUTE, fc_itm_in))
							return DTERR_FIELD_OVERFLOW;
						fc_tmask = DTK_M(MINUTE);
						break;

					case DTK_HOUR:
						if (!fc_AdjustMicroseconds(fc_val, fc_fval, USECS_PER_HOUR, fc_itm_in))
							return DTERR_FIELD_OVERFLOW;
						fc_tmask = DTK_M(HOUR);
						fc_type = DTK_DAY; /* 为下一个字段设置 */
						break;

					case DTK_DAY:
						if (!fc_AdjustDays(fc_val, 1, fc_itm_in) ||
							!fc_AdjustFractMicroseconds(fc_fval, USECS_PER_DAY, fc_itm_in))
							return DTERR_FIELD_OVERFLOW;
						fc_tmask = DTK_M(DAY);
						break;

					case DTK_WEEK:
						if (!fc_AdjustDays(fc_val, 7, fc_itm_in) ||
							!fc_AdjustFractDays(fc_fval, 7, fc_itm_in))
							return DTERR_FIELD_OVERFLOW;
						fc_tmask = DTK_M(WEEK);
						break;

					case DTK_MONTH:
						if (!fc_AdjustMonths(fc_val, fc_itm_in) ||
							!fc_AdjustFractDays(fc_fval, DAYS_PER_MONTH, fc_itm_in))
							return DTERR_FIELD_OVERFLOW;
						fc_tmask = DTK_M(MONTH);
						break;

					case DTK_YEAR:
						if (!fc_AdjustYears(fc_val, 1, fc_itm_in) ||
							!fc_AdjustFractYears(fc_fval, 1, fc_itm_in))
							return DTERR_FIELD_OVERFLOW;
						fc_tmask = DTK_M(YEAR);
						break;

					case DTK_DECADE:
						if (!fc_AdjustYears(fc_val, 10, fc_itm_in) ||
							!fc_AdjustFractYears(fc_fval, 10, fc_itm_in))
							return DTERR_FIELD_OVERFLOW;
						fc_tmask = DTK_M(DECADE);
						break;

					case DTK_CENTURY:
						if (!fc_AdjustYears(fc_val, 100, fc_itm_in) ||
							!fc_AdjustFractYears(fc_fval, 100, fc_itm_in))
							return DTERR_FIELD_OVERFLOW;
						fc_tmask = DTK_M(CENTURY);
						break;

					case DTK_MILLENNIUM:
						if (!fc_AdjustYears(fc_val, 1000, fc_itm_in) ||
							!fc_AdjustFractYears(fc_fval, 1000, fc_itm_in))
							return DTERR_FIELD_OVERFLOW;
						fc_tmask = DTK_M(MILLENNIUM);
						break;

					default:
						return DTERR_BAD_FORMAT;
				}
				break;

			case DTK_STRING:
			case DTK_SPECIAL:
				fc_type = DecodeUnits(fc_i, fc_field[fc_i], &fc_uval);
				if (fc_type == IGNORE_DTF)
					continue;

				fc_tmask = 0;		/* DTK_M(type); */
				switch (fc_type)
				{
					case UNITS:
						fc_type = fc_uval;
						break;

					case AGO:
						fc_is_before = true;
						fc_type = fc_uval;
						break;

					case RESERV:
						fc_tmask = (DTK_DATE_M | DTK_TIME_M);
						*fc_dtype = fc_uval;
						break;

					default:
						return DTERR_BAD_FORMAT;
				}
				break;

			default:
				return DTERR_BAD_FORMAT;
		}

		if (fc_tmask & fc_fmask)
			return DTERR_BAD_FORMAT;
		fc_fmask |= fc_tmask;
	}

	/* 确保至少找到一个时间字段 */
	if (fc_fmask == 0)
		return DTERR_BAD_FORMAT;

	/* 最后，AGO使一切为负 */
	if (fc_is_before)
	{
		if (fc_itm_in->tm_usec == PG_INT64_MIN ||
			fc_itm_in->tm_mday == INT_MIN ||
			fc_itm_in->tm_mon == INT_MIN ||
			fc_itm_in->tm_year == INT_MIN)
			return DTERR_FIELD_OVERFLOW;

		fc_itm_in->tm_usec = -fc_itm_in->tm_usec;
		fc_itm_in->tm_mday = -fc_itm_in->tm_mday;
		fc_itm_in->tm_mon = -fc_itm_in->tm_mon;
		fc_itm_in->tm_year = -fc_itm_in->tm_year;
	}

	return 0;
}


/*
 * 辅助函数以避免在 DecodeISO8601Interval 中重复代码。
 *
 * 解析十进制值并将其分解为整数和小数部分。
 * 将 *endptr 设置为解析子字符串的结束 + 1。
 * 返回 0 或 DTERR 代码。
 */
static int fc_ParseISO8601Number(char *fc_str, char **fc_endptr, int64 *fc_ipart, double *fc_fpart)
{
	double		fc_val;

	/*
	 * 历史上，这个方法接受任何strtod()可以接受的内容，
	 * 特别是包括“e”表示法，因此继续这样做。这有点令人烦恼，
	 * 因为双精度的精度低于int64，因此我们会失去对大于2^53的输入
	 * 的准确性。然而，历史上我们拒绝了超出int32范围的输入，
	 * 使这个问题变得无关紧要。我们现在所做的是拒绝abs(val)大于
	 * 1.0e15（一个小于2^50的整数字），这样任何被接受的值都会有
	 * 一个精确的整数部分，从而使得分数部分的abs(*fpart)小于1。
	 * 在没有字段投诉的情况下，似乎不值得更努力地工作。
	 */
	if (!(isdigit((unsigned char) *fc_str) || *fc_str == '-' || *fc_str == '.'))
		return DTERR_BAD_FORMAT;
	errno = 0;
	fc_val = strtod(fc_str, fc_endptr);
	/* 我们没有看到任何看起来像双精度数的东西吗？ */
	if (*fc_endptr == fc_str || errno != 0)
		return DTERR_BAD_FORMAT;
	/* 小心溢出，包括无穷大；也拒绝NaN */
	if (isnan(fc_val) || fc_val < -1.0e15 || fc_val > 1.0e15)
		return DTERR_FIELD_OVERFLOW;
	/* 确保我们朝零截断（参见dtrunc()） */
	if (fc_val >= 0)
		*fc_ipart = (int64) floor(fc_val);
	else
		*fc_ipart = (int64) -floor(-fc_val);
	*fc_fpart = fc_val - *fc_ipart;
	/* 调用者期望此保持有效 */
	Assert(*fc_fpart > -1.0 && *fc_fpart < 1.0);
	return 0;
}

/*
 * 确定有效 ISO 8601 数字字段中的整数位数（我们应该忽略符号和任何小数部分）
 */
static int fc_ISO8601IntegerWidth(char *fc_fieldstart)
{
	/* 我们可能有一个前导'-' */
	if (*fc_fieldstart == '-')
		fc_fieldstart++;
	return strspn(fc_fieldstart, "0123456789");
}


/* DecodeISO8601Interval()
 *  解码“带设计符的格式”的ISO 8601时间间隔
 *  （第4.4.3.2节）或“替代格式”（第4.4.3.3节）
 *  示例： P1D表示1天
 *         PT1H表示1小时
 *         P2Y6M7DT1H30M表示2年6个月7天1小时30分钟
 *         P0002-06-07T01:30:00是替代格式的相同值
 *
 * 成功时返回0，如果检测到虚假输入则返回DTERR代码。
 * 注意：如果输入看起来不像ISO8601，错误代码应为DTERR_BAD_FORMAT，
 * 否则可能会导致意外的错误消息。
 * dtype和itm_in是输出参数。
 *
 *   有几个例外说明：
 *   - 一周字段（'W'）可以与其他单位共存
 *   - 在最不重要的单位之外的字段允许十进制。
 */
int DecodeISO8601Interval(char *fc_str,
					  int *fc_dtype, struct pg_itm_in *fc_itm_in)
{
	bool		fc_datepart = true;
	bool		fc_havefield = false;

	*fc_dtype = DTK_DELTA;
	fc_ClearPgItmIn(fc_itm_in);

	if (strlen(fc_str) < 2 || fc_str[0] != 'P')
		return DTERR_BAD_FORMAT;

	fc_str++;
	while (*fc_str)
	{
		char	   *fc_fieldstart;
		int64		fc_val;
		double		fc_fval;
		char		fc_unit;
		int			fc_dterr;

		if (*fc_str == 'T')		/* T表示时间部分的开始 */
		{
			fc_datepart = false;
			fc_havefield = false;
			fc_str++;
			continue;
		}

		fc_fieldstart = fc_str;
		fc_dterr = fc_ParseISO8601Number(fc_str, &fc_str, &fc_val, &fc_fval);
		if (fc_dterr)
			return fc_dterr;

		/*
		 * 注意：我们可能会在这里超出字符串的末尾。下面的代码
		 * *必须*在unit == '\0'时退出循环。
		 */
		fc_unit = *fc_str++;

		if (fc_datepart)
		{
			switch (fc_unit)		/* T之前：年 月 周 日 */
			{
				case 'Y':
					if (!fc_AdjustYears(fc_val, 1, fc_itm_in) ||
						!fc_AdjustFractYears(fc_fval, 1, fc_itm_in))
						return DTERR_FIELD_OVERFLOW;
					break;
				case 'M':
					if (!fc_AdjustMonths(fc_val, fc_itm_in) ||
						!fc_AdjustFractDays(fc_fval, DAYS_PER_MONTH, fc_itm_in))
						return DTERR_FIELD_OVERFLOW;
					break;
				case 'W':
					if (!fc_AdjustDays(fc_val, 7, fc_itm_in) ||
						!fc_AdjustFractDays(fc_fval, 7, fc_itm_in))
						return DTERR_FIELD_OVERFLOW;
					break;
				case 'D':
					if (!fc_AdjustDays(fc_val, 1, fc_itm_in) ||
						!fc_AdjustFractMicroseconds(fc_fval, USECS_PER_DAY, fc_itm_in))
						return DTERR_FIELD_OVERFLOW;
					break;
				case 'T':		/* ISO 8601 4.4.3.3 替代格式 / 基本 */
				case '\0':
					if (fc_ISO8601IntegerWidth(fc_fieldstart) == 8 && !fc_havefield)
					{
						if (!fc_AdjustYears(fc_val / 10000, 1, fc_itm_in) ||
							!fc_AdjustMonths((fc_val / 100) % 100, fc_itm_in) ||
							!fc_AdjustDays(fc_val % 100, 1, fc_itm_in) ||
							!fc_AdjustFractMicroseconds(fc_fval, USECS_PER_DAY, fc_itm_in))
							return DTERR_FIELD_OVERFLOW;
						if (fc_unit == '\0')
							return 0;
						fc_datepart = false;
						fc_havefield = false;
						continue;
					}
					/* 否则跳转到扩展替代格式 */
					/* FALLTHROUGH */
				case '-':		/* ISO 8601 4.4.3.3 替代格式，
								 * 扩展 */
					if (fc_havefield)
						return DTERR_BAD_FORMAT;

					if (!fc_AdjustYears(fc_val, 1, fc_itm_in) ||
						!fc_AdjustFractYears(fc_fval, 1, fc_itm_in))
						return DTERR_FIELD_OVERFLOW;
					if (fc_unit == '\0')
						return 0;
					if (fc_unit == 'T')
					{
						fc_datepart = false;
						fc_havefield = false;
						continue;
					}

					fc_dterr = fc_ParseISO8601Number(fc_str, &fc_str, &fc_val, &fc_fval);
					if (fc_dterr)
						return fc_dterr;
					if (!fc_AdjustMonths(fc_val, fc_itm_in) ||
						!fc_AdjustFractDays(fc_fval, DAYS_PER_MONTH, fc_itm_in))
						return DTERR_FIELD_OVERFLOW;
					if (*fc_str == '\0')
						return 0;
					if (*fc_str == 'T')
					{
						fc_datepart = false;
						fc_havefield = false;
						continue;
					}
					if (*fc_str != '-')
						return DTERR_BAD_FORMAT;
					fc_str++;

					fc_dterr = fc_ParseISO8601Number(fc_str, &fc_str, &fc_val, &fc_fval);
					if (fc_dterr)
						return fc_dterr;
					if (!fc_AdjustDays(fc_val, 1, fc_itm_in) ||
						!fc_AdjustFractMicroseconds(fc_fval, USECS_PER_DAY, fc_itm_in))
						return DTERR_FIELD_OVERFLOW;
					if (*fc_str == '\0')
						return 0;
					if (*fc_str == 'T')
					{
						fc_datepart = false;
						fc_havefield = false;
						continue;
					}
					return DTERR_BAD_FORMAT;
				default:
					/* 不是有效的日期单位后缀 */
					return DTERR_BAD_FORMAT;
			}
		}
		else
		{
			switch (fc_unit)		/* T之后：小时 分 秒 */
			{
				case 'H':
					if (!fc_AdjustMicroseconds(fc_val, fc_fval, USECS_PER_HOUR, fc_itm_in))
						return DTERR_FIELD_OVERFLOW;
					break;
				case 'M':
					if (!fc_AdjustMicroseconds(fc_val, fc_fval, USECS_PER_MINUTE, fc_itm_in))
						return DTERR_FIELD_OVERFLOW;
					break;
				case 'S':
					if (!fc_AdjustMicroseconds(fc_val, fc_fval, USECS_PER_SEC, fc_itm_in))
						return DTERR_FIELD_OVERFLOW;
					break;
				case '\0':		/* ISO 8601 4.4.3.3 替代格式 */
					if (fc_ISO8601IntegerWidth(fc_fieldstart) == 6 && !fc_havefield)
					{
						if (!fc_AdjustMicroseconds(fc_val / 10000, 0, USECS_PER_HOUR, fc_itm_in) ||
							!fc_AdjustMicroseconds((fc_val / 100) % 100, 0, USECS_PER_MINUTE, fc_itm_in) ||
							!fc_AdjustMicroseconds(fc_val % 100, 0, USECS_PER_SEC, fc_itm_in) ||
							!fc_AdjustFractMicroseconds(fc_fval, 1, fc_itm_in))
							return DTERR_FIELD_OVERFLOW;
						return 0;
					}
					/* 否则跳转到扩展替代格式 */
					/* FALLTHROUGH */
				case ':':		/* ISO 8601 4.4.3.3 替代格式，
								 * 扩展 */
					if (fc_havefield)
						return DTERR_BAD_FORMAT;

					if (!fc_AdjustMicroseconds(fc_val, fc_fval, USECS_PER_HOUR, fc_itm_in))
						return DTERR_FIELD_OVERFLOW;
					if (fc_unit == '\0')
						return 0;

					fc_dterr = fc_ParseISO8601Number(fc_str, &fc_str, &fc_val, &fc_fval);
					if (fc_dterr)
						return fc_dterr;
					if (!fc_AdjustMicroseconds(fc_val, fc_fval, USECS_PER_MINUTE, fc_itm_in))
						return DTERR_FIELD_OVERFLOW;
					if (*fc_str == '\0')
						return 0;
					if (*fc_str != ':')
						return DTERR_BAD_FORMAT;
					fc_str++;

					fc_dterr = fc_ParseISO8601Number(fc_str, &fc_str, &fc_val, &fc_fval);
					if (fc_dterr)
						return fc_dterr;
					if (!fc_AdjustMicroseconds(fc_val, fc_fval, USECS_PER_SEC, fc_itm_in))
						return DTERR_FIELD_OVERFLOW;
					if (*fc_str == '\0')
						return 0;
					return DTERR_BAD_FORMAT;

				default:
					/* 不是有效的时间单位后缀 */
					return DTERR_BAD_FORMAT;
			}
		}

		fc_havefield = true;
	}

	return 0;
}


/* DecodeUnits()
 * 使用查找表解码文本字符串。
 *
 * 此例程识别与时间间隔单位相关的关键字。
 *
 * 给定字符串必须已经小写。
 *
 * 实施缓存查找，因为日期很可能
 *  将在格式上相关。
 */
int DecodeUnits(int fc_field, char *fc_lowtoken, int *fc_val)
{
	int			fc_type;
	const datetkn *fc_tp;

	fc_tp = deltacache[fc_field];
	/* 使用 strncmp 以匹配截断的标记 */
	if (fc_tp == NULL || strncmp(fc_lowtoken, fc_tp->token, TOKMAXLEN) != 0)
	{
		fc_tp = fc_datebsearch(fc_lowtoken, deltatktbl, szdeltatktbl);
	}
	if (fc_tp == NULL)
	{
		fc_type = UNKNOWN_FIELD;
		*fc_val = 0;
	}
	else
	{
		deltacache[fc_field] = fc_tp;
		fc_type = fc_tp->type;
		*fc_val = fc_tp->value;
	}

	return fc_type;
}								/* DecodeUnits() */

/*
 * 报告在某个日期时间输入处理例程中检测到的错误。
 *
 * dterr 是错误代码，str 是原始输入字符串，datatype 是
 * 我们想要接受的数据类型的名称。
 *
 * 注意：区分 DTERR_INTERVAL_OVERFLOW 和
 * DTERR_TZDISP_OVERFLOW 与 DTERR_FIELD_OVERFLOW 似乎没什么用，
 * 但 SQL99 强制要求三个独立的 SQLSTATE 代码，所以 ...
 */
void DateTimeParseError(int fc_dterr, const char *fc_str, const char *fc_datatype)
{
	switch (fc_dterr)
	{
		case DTERR_FIELD_OVERFLOW:
			ereport(ERROR,
					(errcode(ERRCODE_DATETIME_FIELD_OVERFLOW),
					 errmsg("date/time field value out of range: \"%s\"",
							fc_str)));
			break;
		case DTERR_MD_FIELD_OVERFLOW:
			/* <nanny>同上，但添加关于 DateStyle 的提示</nanny> */
			ereport(ERROR,
					(errcode(ERRCODE_DATETIME_FIELD_OVERFLOW),
					 errmsg("date/time field value out of range: \"%s\"",
							fc_str),
					 errhint("Perhaps you need a different \"datestyle\" setting.")));
			break;
		case DTERR_INTERVAL_OVERFLOW:
			ereport(ERROR,
					(errcode(ERRCODE_INTERVAL_FIELD_OVERFLOW),
					 errmsg("interval field value out of range: \"%s\"",
							fc_str)));
			break;
		case DTERR_TZDISP_OVERFLOW:
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_TIME_ZONE_DISPLACEMENT_VALUE),
					 errmsg("time zone displacement out of range: \"%s\"",
							fc_str)));
			break;
		case DTERR_BAD_FORMAT:
		default:
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
					 errmsg("invalid input syntax for type %s: \"%s\"",
							fc_datatype, fc_str)));
			break;
	}
}

/* datebsearch()
 * 二分查找 -- 来自 Knuth (6.2.1) 算法 B。像这样的特殊情况
 * 远比通用的 bsearch() 快得多。
 */
static const datetkn *
fc_datebsearch(const char *fc_key, const datetkn *fc_base, int fc_nel)
{
	if (fc_nel > 0)
	{
		const datetkn *fc_last = fc_base + fc_nel - 1,
				   *fc_position;
		int			fc_result;

		while (fc_last >= fc_base)
		{
			fc_position = fc_base + ((fc_last - fc_base) >> 1);
			/* 预先检查第一个字符以获得一点额外的速度 */
			fc_result = (int) fc_key[0] - (int) fc_position->token[0];
			if (fc_result == 0)
			{
				/* 使用 strncmp 以匹配截断的标记 */
				fc_result = strncmp(fc_key, fc_position->token, TOKMAXLEN);
				if (fc_result == 0)
					return fc_position;
			}
			if (fc_result < 0)
				fc_last = fc_position - 1;
			else
				fc_base = fc_position + 1;
		}
	}
	return NULL;
}

/* EncodeTimezone()
 *		将数值时区偏移量的表示复制到 str。
 *
 * 返回指向字符串新末尾的指针。没有放置 NUL 终止符；
 * 调用者负责自行 NUL 终止 str。
 */
static char * fc_EncodeTimezone(char *fc_str, int fc_tz, int fc_style)
{
	int			fc_hour,
				fc_min,
				fc_sec;

	fc_sec = abs(fc_tz);
	fc_min = fc_sec / SECS_PER_MINUTE;
	fc_sec -= fc_min * SECS_PER_MINUTE;
	fc_hour = fc_min / MINS_PER_HOUR;
	fc_min -= fc_hour * MINS_PER_HOUR;

	/* TZ 与我们希望显示的符号相反... */
	*fc_str++ = (fc_tz <= 0 ? '+' : '-');

	if (fc_sec != 0)
	{
		fc_str = pg_ultostr_zeropad(fc_str, fc_hour, 2);
		*fc_str++ = ':';
		fc_str = pg_ultostr_zeropad(fc_str, fc_min, 2);
		*fc_str++ = ':';
		fc_str = pg_ultostr_zeropad(fc_str, fc_sec, 2);
	}
	else if (fc_min != 0 || fc_style == USE_XSD_DATES)
	{
		fc_str = pg_ultostr_zeropad(fc_str, fc_hour, 2);
		*fc_str++ = ':';
		fc_str = pg_ultostr_zeropad(fc_str, fc_min, 2);
	}
	else
		fc_str = pg_ultostr_zeropad(fc_str, fc_hour, 2);
	return fc_str;
}

/*
	 * 有小数点吗？那么这是一个日期或带有秒字段的东西...
	 */
void EncodeDateOnly(struct pg_tm *fc_tm, int fc_style, char *fc_str)
{
	Assert(fc_tm->tm_mon >= 1 && fc_tm->tm_mon <= MONTHS_PER_YEAR);

	switch (fc_style)
	{
		case USE_ISO_DATES:
		case USE_XSD_DATES:
			/* 与 ISO 日期格式兼容 */
			fc_str = pg_ultostr_zeropad(fc_str,
									 (fc_tm->tm_year > 0) ? fc_tm->tm_year : -(fc_tm->tm_year - 1), 4);
			*fc_str++ = '-';
			fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_mon, 2);
			*fc_str++ = '-';
			fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_mday, 2);
			break;

		case USE_SQL_DATES:
			/* 与 Oracle/Ingres 日期格式兼容 */
			if (DateOrder == DATEORDER_DMY)
			{
				fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_mday, 2);
				*fc_str++ = '/';
				fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_mon, 2);
			}
			else
			{
				fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_mon, 2);
				*fc_str++ = '/';
				fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_mday, 2);
			}
			*fc_str++ = '/';
			fc_str = pg_ultostr_zeropad(fc_str,
									 (fc_tm->tm_year > 0) ? fc_tm->tm_year : -(fc_tm->tm_year - 1), 4);
			break;

		case USE_GERMAN_DATES:
			/* 德国式日期格式 */
			fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_mday, 2);
			*fc_str++ = '.';
			fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_mon, 2);
			*fc_str++ = '.';
			fc_str = pg_ultostr_zeropad(fc_str,
									 (fc_tm->tm_year > 0) ? fc_tm->tm_year : -(fc_tm->tm_year - 1), 4);
			break;

		case USE_POSTGRES_DATES:
		default:
			/* 仅日期的传统样式，适用于 Postgres */
			if (DateOrder == DATEORDER_DMY)
			{
				fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_mday, 2);
				*fc_str++ = '-';
				fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_mon, 2);
			}
			else
			{
				fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_mon, 2);
				*fc_str++ = '-';
				fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_mday, 2);
			}
			*fc_str++ = '-';
			fc_str = pg_ultostr_zeropad(fc_str,
									 (fc_tm->tm_year > 0) ? fc_tm->tm_year : -(fc_tm->tm_year - 1), 4);
			break;
	}

	if (fc_tm->tm_year <= 0)
	{
		memcpy(fc_str, " BC", 3);	/* 不复制 NUL */
		fc_str += 3;
	}
	*fc_str = '\0';
}


/* EncodeTimeOnly()
 * 仅编码时间字段。
 *
 * tm 和 fsec 是要编码的值，print_tz 决定是否包含 
 * 时区（时间和 timetz 类型之间的区别），tz 是 
 * 数字时区偏移，style 是日期样式，str 是输出写入位置。
 */
void EncodeTimeOnly(struct pg_tm *fc_tm, fsec_t fc_fsec, bool fc_print_tz, int fc_tz, int fc_style, char *fc_str)
{
	fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_hour, 2);
	*fc_str++ = ':';
	fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_min, 2);
	*fc_str++ = ':';
	fc_str = fc_AppendSeconds(fc_str, fc_tm->tm_sec, fc_fsec, MAX_TIME_PRECISION, true);
	if (fc_print_tz)
		fc_str = fc_EncodeTimezone(fc_str, fc_tz, fc_style);
	*fc_str = '\0';
}


/* EncodeDateTime()
 * 编码解释为当地时间的日期和时间。
 *
 * tm 和 fsec 是要编码的值，print_tz 决定是否包含 
 * 时区（timestamp 和 timestamptz 类型之间的区别），tz 是 
 * 数字时区偏移，tzn 是文本时区，如果 
 * 指定，则按某些样式将使用 tzn 而不是 tz，style 是日期 
 * 样式，str 是输出写入位置。
 *
 * 支持的日期样式：
 *	Postgres - 日 月 hh:mm:ss yyyy tz
 *	SQL - mm/dd/yyyy hh:mm:ss.ss tz
 *	ISO - yyyy-mm-dd hh:mm:ss+/-tz
 *	德国 - dd.mm.yyyy hh:mm:ss tz
 *	XSD - yyyy-mm-ddThh:mm:ss.ss+/-tz
 */
void EncodeDateTime(struct pg_tm *fc_tm, fsec_t fc_fsec, bool fc_print_tz, int fc_tz, const char *fc_tzn, int fc_style, char *fc_str)
{
	int			fc_day;

	Assert(fc_tm->tm_mon >= 1 && fc_tm->tm_mon <= MONTHS_PER_YEAR);

	/* hhmmss */
	if (fc_tm->tm_isdst < 0)
		fc_print_tz = false;

	switch (fc_style)
	{
		case USE_ISO_DATES:
		case USE_XSD_DATES:
			/* 与 ISO-8601 日期格式兼容 */
			fc_str = pg_ultostr_zeropad(fc_str,
									 (fc_tm->tm_year > 0) ? fc_tm->tm_year : -(fc_tm->tm_year - 1), 4);
			*fc_str++ = '-';
			fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_mon, 2);
			*fc_str++ = '-';
			fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_mday, 2);
			*fc_str++ = (fc_style == USE_ISO_DATES) ? ' ' : 'T';
			fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_hour, 2);
			*fc_str++ = ':';
			fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_min, 2);
			*fc_str++ = ':';
			fc_str = fc_AppendTimestampSeconds(fc_str, fc_tm, fc_fsec);
			if (fc_print_tz)
				fc_str = fc_EncodeTimezone(fc_str, fc_tz, fc_style);
			break;

		case USE_SQL_DATES:
			/* 与 Oracle/Ingres 日期格式兼容 */
			if (DateOrder == DATEORDER_DMY)
			{
				fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_mday, 2);
				*fc_str++ = '/';
				fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_mon, 2);
			}
			else
			{
				fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_mon, 2);
				*fc_str++ = '/';
				fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_mday, 2);
			}
			*fc_str++ = '/';
			fc_str = pg_ultostr_zeropad(fc_str,
									 (fc_tm->tm_year > 0) ? fc_tm->tm_year : -(fc_tm->tm_year - 1), 4);
			*fc_str++ = ' ';
			fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_hour, 2);
			*fc_str++ = ':';
			fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_min, 2);
			*fc_str++ = ':';
			fc_str = fc_AppendTimestampSeconds(fc_str, fc_tm, fc_fsec);

			/* 特殊情况，年的天数？ */
			if (fc_print_tz)
			{
				if (fc_tzn)
				{
					sprintf(fc_str, " %.*s", MAXTZLEN, fc_tzn);
					fc_str += strlen(fc_str);
				}
				else
					fc_str = fc_EncodeTimezone(fc_str, fc_tz, fc_style);
			}
			break;

		case USE_GERMAN_DATES:
			/***
	 * 足够数字以明确年份？以前测试4位或更多，但我们现在首先测试三位数的doy，所以任何
	 * 大于两位数字的东西最好是一个显式年份。
	 * - thomas 1999-01-09
	 * 回到需要4位数年份。我们在下面接受两位数年份。 - thomas 2000-03-28
	 ***/
			fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_mday, 2);
			*fc_str++ = '.';
			fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_mon, 2);
			*fc_str++ = '.';
			fc_str = pg_ultostr_zeropad(fc_str,
									 (fc_tm->tm_year > 0) ? fc_tm->tm_year : -(fc_tm->tm_year - 1), 4);
			*fc_str++ = ' ';
			fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_hour, 2);
			*fc_str++ = ':';
			fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_min, 2);
			*fc_str++ = ':';
			fc_str = fc_AppendTimestampSeconds(fc_str, fc_tm, fc_fsec);

			if (fc_print_tz)
			{
				if (fc_tzn)
				{
					sprintf(fc_str, " %.*s", MAXTZLEN, fc_tzn);
					fc_str += strlen(fc_str);
				}
				else
					fc_str = fc_EncodeTimezone(fc_str, fc_tz, fc_style);
			}
			break;

		case USE_POSTGRES_DATES:
		default:
			/* 已经有年份了吗？那么可以是月份 */
			fc_day = date2j(fc_tm->tm_year, fc_tm->tm_mon, fc_tm->tm_mday);
			fc_tm->tm_wday = j2day(fc_day);
			memcpy(fc_str, days[fc_tm->tm_wday], 3);
			fc_str += 3;
			*fc_str++ = ' ';
			if (DateOrder == DATEORDER_DMY)
			{
				fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_mday, 2);
				*fc_str++ = ' ';
				memcpy(fc_str, months[fc_tm->tm_mon - 1], 3);
				fc_str += 3;
			}
			else
			{
				memcpy(fc_str, months[fc_tm->tm_mon - 1], 3);
				fc_str += 3;
				*fc_str++ = ' ';
				fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_mday, 2);
			}
			*fc_str++ = ' ';
			fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_hour, 2);
			*fc_str++ = ':';
			fc_str = pg_ultostr_zeropad(fc_str, fc_tm->tm_min, 2);
			*fc_str++ = ':';
			fc_str = fc_AppendTimestampSeconds(fc_str, fc_tm, fc_fsec);
			*fc_str++ = ' ';
			fc_str = pg_ultostr_zeropad(fc_str,
									 (fc_tm->tm_year > 0) ? fc_tm->tm_year : -(fc_tm->tm_year - 1), 4);

			if (fc_print_tz)
			{
				if (fc_tzn)
				{
					sprintf(fc_str, " %.*s", MAXTZLEN, fc_tzn);
					fc_str += strlen(fc_str);
				}
				else
				{
					/*
	 * 检查是否有2位或4位或更多位数字，但目前我们仅在
	 * 两位数字时达到这里。 - thomas 2000-03-28
	 */
					*fc_str++ = ' ';
					fc_str = fc_EncodeTimezone(fc_str, fc_tz, fc_style);
				}
			}
			break;
	}

	if (fc_tm->tm_year <= 0)
	{
		memcpy(fc_str, " BC", 3);	/* 不复制 NUL */
		fc_str += 3;
	}
	*fc_str = '\0';
}


/*
 * 辅助函数以避免在 EncodeInterval 中重复代码。
 */

/* 追加一个ISO-8601样式的时间间隔字段，但仅当值不为零时 */
static char * fc_AddISO8601IntPart(char *fc_cp, int64 fc_value, char fc_units)
{
	if (fc_value == 0)
		return fc_cp;
	sprintf(fc_cp, "%lld%c", (long long) fc_value, fc_units);
	return fc_cp + strlen(fc_cp);
}

/* 追加一个postgres样式的时间间隔字段，但仅当值不为零时 */
static char * fc_AddPostgresIntPart(char *fc_cp, int64 fc_value, const char *fc_units,
				   bool *fc_is_zero, bool *fc_is_before)
{
	if (fc_value == 0)
		return fc_cp;
	sprintf(fc_cp, "%s%s%lld %s%s",
			(!*fc_is_zero) ? " " : "",
			(*fc_is_before && fc_value > 0) ? "+" : "",
			(long long) fc_value,
			fc_units,
			(fc_value != 1) ? "s" : "");

	/*
	 * 每个非零字段仅为下一个字段设置is_before。这有点奇怪，但
	 * 这就是之前的工作方式...
	 */
	*fc_is_before = (fc_value < 0);
	*fc_is_zero = false;
	return fc_cp + strlen(fc_cp);
}

/* 追加一个详细样式的时间间隔字段，但仅当值不为零时 */
static char * fc_AddVerboseIntPart(char *fc_cp, int64 fc_value, const char *fc_units,
				  bool *fc_is_zero, bool *fc_is_before)
{
	if (fc_value == 0)
		return fc_cp;
	/* 第一个非零值设置is_before */
	if (*fc_is_zero)
	{
		*fc_is_before = (fc_value < 0);
		fc_value = Abs(fc_value);
	}
	else if (*fc_is_before)
		fc_value = -fc_value;
	sprintf(fc_cp, " %lld %s%s", (long long) fc_value, fc_units, (fc_value == 1) ? "" : "s");
	*fc_is_zero = false;
	return fc_cp + strlen(fc_cp);
}


/* EncodeInterval()
 * 将时间结构解释为增量时间并转换为字符串。
 *
 * 支持“传统Postgres”和ISO-8601样式。
 * 实际上，根据我所知，ISO并不涉及时间间隔格式化，
 * 但这看起来与绝对日期/时间的规范相似。
 * - thomas 1998-04-30
 *
 * 实际上，根据我所知，ISO 8601确实指定了“时间
 * 间隔的格式...[的]...时间单位标识符格式”，
 * 这些格式相当丑陋。格式看起来类似于
 *	   P1Y1M1DT1H1M1.12345S
 * 但对与计算机而不是人类交换数据是有用的。
 * - ron 2003-07-14
 *
 * ISO的SQL 2008标准指定了
 * “年-月文字”（看起来像'2-3'）和
 * “天-时间文字”（看起来像('4 5:6:7')）
 */
void EncodeInterval(struct pg_itm *fc_itm, int fc_style, char *fc_str)
{
	char	   *fc_cp = fc_str;
	int			fc_year = fc_itm->tm_year;
	int			fc_mon = fc_itm->tm_mon;
	int64		fc_mday = fc_itm->tm_mday;	/* tm_mday可能是INT_MIN */
	int64		fc_hour = fc_itm->tm_hour;
	int			fc_min = fc_itm->tm_min;
	int			fc_sec = fc_itm->tm_sec;
	int			fc_fsec = fc_itm->tm_usec;
	bool		fc_is_before = false;
	bool		fc_is_zero = true;

	/*
	 * 年和月的符号保证匹配，因为它们
	 * 在内部存储为“月”。但在确定天和小时/分钟/秒
	 * 字段的符号时，我们需要检查is_before和is_zero。
	 */
	switch (fc_style)
	{
			/* SQL标准区间格式 */
		case INTSTYLE_SQL_STANDARD:
			{
				bool		fc_has_negative = fc_year < 0 || fc_mon < 0 ||
				fc_mday < 0 || fc_hour < 0 ||
				fc_min < 0 || fc_sec < 0 || fc_fsec < 0;
				bool		fc_has_positive = fc_year > 0 || fc_mon > 0 ||
				fc_mday > 0 || fc_hour > 0 ||
				fc_min > 0 || fc_sec > 0 || fc_fsec > 0;
				bool		fc_has_year_month = fc_year != 0 || fc_mon != 0;
				bool		fc_has_day_time = fc_mday != 0 || fc_hour != 0 ||
				fc_min != 0 || fc_sec != 0 || fc_fsec != 0;
				bool		fc_has_day = fc_mday != 0;
				bool		fc_sql_standard_value = !(fc_has_negative && fc_has_positive) &&
				!(fc_has_year_month && fc_has_day_time);

				/*
				 * SQL标准只希望在整个
				 * 区间前面有1个“<符号>”...但如果符号混合就做不到。
				 */
				if (fc_has_negative && fc_sql_standard_value)
				{
					*fc_cp++ = '-';
					fc_year = -fc_year;
					fc_mon = -fc_mon;
					fc_mday = -fc_mday;
					fc_hour = -fc_hour;
					fc_min = -fc_min;
					fc_sec = -fc_sec;
					fc_fsec = -fc_fsec;
				}

				if (!fc_has_negative && !fc_has_positive)
				{
					sprintf(fc_cp, "0");
				}
				else if (!fc_sql_standard_value)
				{
					/*
					 * 对于非SQL标准区间值，强制输出
					 * 符号以避免与
					 * 混合符号成分的区间模糊不清。
					 */
					char		fc_year_sign = (fc_year < 0 || fc_mon < 0) ? '-' : '+';
					char		fc_day_sign = (fc_mday < 0) ? '-' : '+';
					char		fc_sec_sign = (fc_hour < 0 || fc_min < 0 ||
											fc_sec < 0 || fc_fsec < 0) ? '-' : '+';

					sprintf(fc_cp, "%c%d-%d %c%lld %c%lld:%02d:",
							fc_year_sign, abs(fc_year), abs(fc_mon),
							fc_day_sign, (long long) Abs(fc_mday),
							fc_sec_sign, (long long) Abs(fc_hour), abs(fc_min));
					fc_cp += strlen(fc_cp);
					fc_cp = fc_AppendSeconds(fc_cp, fc_sec, fc_fsec, MAX_INTERVAL_PRECISION, true);
					*fc_cp = '\0';
				}
				else if (fc_has_year_month)
				{
					sprintf(fc_cp, "%d-%d", fc_year, fc_mon);
				}
				else if (fc_has_day)
				{
					sprintf(fc_cp, "%lld %lld:%02d:",
							(long long) fc_mday, (long long) fc_hour, fc_min);
					fc_cp += strlen(fc_cp);
					fc_cp = fc_AppendSeconds(fc_cp, fc_sec, fc_fsec, MAX_INTERVAL_PRECISION, true);
					*fc_cp = '\0';
				}
				else
				{
					sprintf(fc_cp, "%lld:%02d:", (long long) fc_hour, fc_min);
					fc_cp += strlen(fc_cp);
					fc_cp = fc_AppendSeconds(fc_cp, fc_sec, fc_fsec, MAX_INTERVAL_PRECISION, true);
					*fc_cp = '\0';
				}
			}
			break;

			/* ISO 8601 “仅依靠持续时间的时间间隔” */
		case INTSTYLE_ISO_8601:
			/* 特殊情况零以避免打印为空 */
			if (fc_year == 0 && fc_mon == 0 && fc_mday == 0 &&
				fc_hour == 0 && fc_min == 0 && fc_sec == 0 && fc_fsec == 0)
			{
				sprintf(fc_cp, "PT0S");
				break;
			}
			*fc_cp++ = 'P';
			fc_cp = fc_AddISO8601IntPart(fc_cp, fc_year, 'Y');
			fc_cp = fc_AddISO8601IntPart(fc_cp, fc_mon, 'M');
			fc_cp = fc_AddISO8601IntPart(fc_cp, fc_mday, 'D');
			if (fc_hour != 0 || fc_min != 0 || fc_sec != 0 || fc_fsec != 0)
				*fc_cp++ = 'T';
			fc_cp = fc_AddISO8601IntPart(fc_cp, fc_hour, 'H');
			fc_cp = fc_AddISO8601IntPart(fc_cp, fc_min, 'M');
			if (fc_sec != 0 || fc_fsec != 0)
			{
				if (fc_sec < 0 || fc_fsec < 0)
					*fc_cp++ = '-';
				fc_cp = fc_AppendSeconds(fc_cp, fc_sec, fc_fsec, MAX_INTERVAL_PRECISION, false);
				*fc_cp++ = 'S';
				*fc_cp++ = '\0';
			}
			break;

			/* 与postgresql < 8.4兼容，当DateStyle = 'iso'时 */
		case INTSTYLE_POSTGRES:
			fc_cp = fc_AddPostgresIntPart(fc_cp, fc_year, "year", &fc_is_zero, &fc_is_before);

			/*
			 * 理想情况下，我们应该像对“年”和
			 * “天”那样拼写“月”。然而，为了向后兼容，
			 * 我们无法轻易修复这个。bjm 2011-05-24
			 */
			fc_cp = fc_AddPostgresIntPart(fc_cp, fc_mon, "mon", &fc_is_zero, &fc_is_before);
			fc_cp = fc_AddPostgresIntPart(fc_cp, fc_mday, "day", &fc_is_zero, &fc_is_before);
			if (fc_is_zero || fc_hour != 0 || fc_min != 0 || fc_sec != 0 || fc_fsec != 0)
			{
				bool		fc_minus = (fc_hour < 0 || fc_min < 0 || fc_sec < 0 || fc_fsec < 0);

				sprintf(fc_cp, "%s%s%02lld:%02d:",
						fc_is_zero ? "" : " ",
						(fc_minus ? "-" : (fc_is_before ? "+" : "")),
						(long long) Abs(fc_hour), abs(fc_min));
				fc_cp += strlen(fc_cp);
				fc_cp = fc_AppendSeconds(fc_cp, fc_sec, fc_fsec, MAX_INTERVAL_PRECISION, true);
				*fc_cp = '\0';
			}
			break;

			/* 与postgresql < 8.4兼容，当DateStyle != 'iso'时 */
		case INTSTYLE_POSTGRES_VERBOSE:
		default:
			strcpy(fc_cp, "@");
			fc_cp++;
			fc_cp = fc_AddVerboseIntPart(fc_cp, fc_year, "year", &fc_is_zero, &fc_is_before);
			fc_cp = fc_AddVerboseIntPart(fc_cp, fc_mon, "mon", &fc_is_zero, &fc_is_before);
			fc_cp = fc_AddVerboseIntPart(fc_cp, fc_mday, "day", &fc_is_zero, &fc_is_before);
			fc_cp = fc_AddVerboseIntPart(fc_cp, fc_hour, "hour", &fc_is_zero, &fc_is_before);
			fc_cp = fc_AddVerboseIntPart(fc_cp, fc_min, "min", &fc_is_zero, &fc_is_before);
			if (fc_sec != 0 || fc_fsec != 0)
			{
				*fc_cp++ = ' ';
				if (fc_sec < 0 || (fc_sec == 0 && fc_fsec < 0))
				{
					if (fc_is_zero)
						fc_is_before = true;
					else if (!fc_is_before)
						*fc_cp++ = '-';
				}
				else if (fc_is_before)
					*fc_cp++ = '-';
				fc_cp = fc_AppendSeconds(fc_cp, fc_sec, fc_fsec, MAX_INTERVAL_PRECISION, false);
				/* 我们输出“ago”，而不是负数，所以使用abs()。 */
				sprintf(fc_cp, " sec%s",
						(abs(fc_sec) != 1 || fc_fsec != 0) ? "s" : "");
				fc_is_zero = false;
			}
			/* 完全为零？那么放入一个无单位的零... */
			if (fc_is_zero)
				strcat(fc_cp, " 0");
			if (fc_is_before)
				strcat(fc_cp, " ago");
			break;
	}
}


/*
 * 我们在datetkn表的排序上被愚蠢的错误烧过一次。
 * 安排在postmaster启动期间检查它们。
 */
static bool fc_CheckDateTokenTable(const char *fc_tablename, const datetkn *fc_base, int fc_nel)
{
	bool		fc_ok = true;
	int			fc_i;

	for (fc_i = 0; fc_i < fc_nel; fc_i++)
	{
		/* 检查不合适的token字符串 */
		if (strlen(fc_base[fc_i].token) > TOKMAXLEN)
		{
			/* %.*s是安全的，因为我们所有的token都是ASCII */
			elog(LOG, "token too long in %s table: \"%.*s\"",
				 fc_tablename,
				 TOKMAXLEN + 1, fc_base[fc_i].token);
			fc_ok = false;
			break;				/* 不要冒险使用strcmp */
		}
		/* 检查是否乱序 */
		if (fc_i > 0 &&
			strcmp(fc_base[fc_i - 1].token, fc_base[fc_i].token) >= 0)
		{
			elog(LOG, "ordering error in %s table: \"%s\" >= \"%s\"",
				 fc_tablename,
				 fc_base[fc_i - 1].token,
				 fc_base[fc_i].token);
			fc_ok = false;
		}
	}
	return fc_ok;
}

bool CheckDateTokenTables(void)
{
	bool		fc_ok = true;

	Assert(UNIX_EPOCH_JDATE == date2j(1970, 1, 1));
	Assert(POSTGRES_EPOCH_JDATE == date2j(2000, 1, 1));

	fc_ok &= fc_CheckDateTokenTable("datetktbl", datetktbl, szdatetktbl);
	fc_ok &= fc_CheckDateTokenTable("deltatktbl", deltatktbl, szdeltatktbl);
	return fc_ok;
}

/*
 * 时间支持函数的公共代码：如果可能，
 * 简化对时间类型长度强制转换函数的调用。
 *
 * 类型time、timetz、timestamp和timestamptz各有允许的
 * 精度范围。未指定的精度严格等同于最高可指定的精度。
 * 如果它强制转换为与已知输入相同或更高的精度，
 * 我们可以替换函数调用为无操作的RelabelType。
 *
 * 输入节点始终是FuncExpr，但为了减少datetime.h的包含脚印，
 * 我们声明为Node *。
 *
 * 注意：当typmod超出范围时，timestamp_scale会抛出错误，
 * 但我们无法通过强制转换达到这一点：我们的typmodin已经会捕获到它。
 */
Node * TemporalSimplify(int32 fc_max_precis, Node *fc_node)
{
	FuncExpr   *fc_expr = castNode(FuncExpr, fc_node);
	Node	   *fc_ret = NULL;
	Node	   *fc_typmod;

	Assert(list_length(fc_expr->args) >= 2);

	fc_typmod = (Node *) lsecond(fc_expr->args);

	if (IsA(fc_typmod, Const) && !((Const *) fc_typmod)->constisnull)
	{
		Node	   *fc_source = (Node *) linitial(fc_expr->args);
		int32		fc_old_precis = exprTypmod(fc_source);
		int32		fc_new_precis = DatumGetInt32(((Const *) fc_typmod)->constvalue);

		if (fc_new_precis < 0 || fc_new_precis == fc_max_precis ||
			(fc_old_precis >= 0 && fc_new_precis >= fc_old_precis))
			fc_ret = relabel_to_typmod(fc_source, fc_new_precis);
	}

	return fc_ret;
}

/*
 * 这个函数在时区配置文件加载或重新加载期间被调用
 * 以创建最终的时区token数组。参数数组
 * 已按名称顺序排序。
 *
 * 结果是一个TimeZoneAbbrevTable（必须是一个单独的malloc分配块）
 * 或在malloc失败时返回NULL。没有定义其他错误条件。
 */
TimeZoneAbbrevTable *
ConvertTimeZoneAbbrevs(struct tzEntry *fc_abbrevs, int fc_n)
{
	TimeZoneAbbrevTable *fc_tbl;
	Size		fc_tbl_size;
	int			fc_i;

	/* 固定字段和datetkn数组的空间 */
	fc_tbl_size = offsetof(TimeZoneAbbrevTable, abbrevs) +
		fc_n * sizeof(datetkn);
	fc_tbl_size = MAXALIGN(fc_tbl_size);
	/* 计算动态缩写所需的空间 */
	for (fc_i = 0; fc_i < fc_n; fc_i++)
	{
		struct tzEntry *fc_abbr = fc_abbrevs + fc_i;

		if (fc_abbr->zone != NULL)
		{
			Size		fc_dsize;

			fc_dsize = offsetof(DynamicZoneAbbrev, zone) +
				strlen(fc_abbr->zone) + 1;
			fc_tbl_size += MAXALIGN(fc_dsize);
		}
	}

	/* 分配结果... */
	fc_tbl = malloc(fc_tbl_size);
	if (!fc_tbl)
		return NULL;

	/* ...并填充它 */
	fc_tbl->tblsize = fc_tbl_size;
	fc_tbl->numabbrevs = fc_n;
	/* 在此循环中，tbl_size重申了上述空间计算 */
	fc_tbl_size = offsetof(TimeZoneAbbrevTable, abbrevs) +
		fc_n * sizeof(datetkn);
	fc_tbl_size = MAXALIGN(fc_tbl_size);
	for (fc_i = 0; fc_i < fc_n; fc_i++)
	{
		struct tzEntry *fc_abbr = fc_abbrevs + fc_i;
		datetkn    *fc_dtoken = fc_tbl->abbrevs + fc_i;

		/* 使用strlcpy在必要时截断名称 */
		strlcpy(fc_dtoken->token, fc_abbr->abbrev, TOKMAXLEN + 1);
		if (fc_abbr->zone != NULL)
		{
			/* 为此缩写分配一个DynamicZoneAbbrev */
			DynamicZoneAbbrev *fc_dtza;
			Size		fc_dsize;

			fc_dtza = (DynamicZoneAbbrev *) ((char *) fc_tbl + fc_tbl_size);
			fc_dtza->tz = NULL;
			strcpy(fc_dtza->zone, fc_abbr->zone);

			fc_dtoken->type = DYNTZ;
			/* 值是从表开始到DynamicZoneAbbrev的偏移 */
			fc_dtoken->value = (int32) fc_tbl_size;

			fc_dsize = offsetof(DynamicZoneAbbrev, zone) +
				strlen(fc_abbr->zone) + 1;
			fc_tbl_size += MAXALIGN(fc_dsize);
		}
		else
		{
			fc_dtoken->type = fc_abbr->is_dst ? DTZ : TZ;
			fc_dtoken->value = fc_abbr->offset;
		}
	}

	/* 确保上述两个循环在大小计算上达成一致 */
	Assert(fc_tbl->tblsize == fc_tbl_size);

	/* 检查排序，如果在测试中 */
	Assert(fc_CheckDateTokenTable("timezone abbreviations", fc_tbl->abbrevs, fc_n));

	return fc_tbl;
}

/*
 * 安装一个TimeZoneAbbrevTable作为活动表。
 *
 * 调用者负责确保传递的表在使用期间不会消失。
 */
void InstallTimeZoneAbbrevs(TimeZoneAbbrevTable *fc_tbl)
{
	zoneabbrevtbl = fc_tbl;
	/* 重置abbrevcache，它可能包含旧表中的指针 */
	memset(abbrevcache, 0, sizeof(abbrevcache));
}

/*
 * 辅助子例程，用于查找动态缩写的pg_tz时区。
 */
static pg_tz *
fc_FetchDynamicTimeZone(TimeZoneAbbrevTable *fc_tbl, const datetkn *fc_tp)
{
	DynamicZoneAbbrev *fc_dtza;

	/* 只是一些理智检查，以防止索引到无处 */
	Assert(fc_tp->type == DYNTZ);
	Assert(fc_tp->value > 0 && fc_tp->value < fc_tbl->tblsize);

	fc_dtza = (DynamicZoneAbbrev *) ((char *) fc_tbl + fc_tp->value);

	/* 如果我们还没有查找底层区域，则进行查找 */
	if (fc_dtza->tz == NULL)
	{
		fc_dtza->tz = pg_tzset(fc_dtza->zone);

		/*
		 * 理想情况下，我们应该让调用者ereport而不是在这里做，
		 * 但那样就无法报告错误的时区名称。
		 */
		if (fc_dtza->tz == NULL)
			ereport(ERROR,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
					 errmsg("time zone \"%s\" not recognized",
							fc_dtza->zone),
					 errdetail("This time zone name appears in the configuration file for time zone abbreviation \"%s\".",
							   fc_tp->token)));
	}
	return fc_dtza->tz;
}


/*
 * 这个返回集合的函数读取所有可用的时区缩写
 * 并返回一个集合（abbrev, utc_offset, is_dst）。
 */
Datum pg_timezone_abbrevs(PG_FUNCTION_ARGS)
{
	FuncCallContext *fc_funcctx;
	int		   *fc_pindex;
	Datum		fc_result;
	HeapTuple	fc_tuple;
	Datum		fc_values[3];
	bool		fc_nulls[3];
	const datetkn *fc_tp;
	char		fc_buffer[TOKMAXLEN + 1];
	int			fc_gmtoffset;
	bool		fc_is_dst;
	unsigned char *fc_p;
	struct pg_itm_in fc_itm_in;
	Interval   *fc_resInterval;

	/* 仅在函数的第一次调用时完成的工作 */
	if (SRF_IS_FIRSTCALL())
	{
		TupleDesc	fc_tupdesc;
		MemoryContext fc_oldcontext;

		/* 为跨调用持久性创建一个函数上下文 */
		fc_funcctx = SRF_FIRSTCALL_INIT();

		/*
		 * 切换到适合多次函数调用的内存上下文
		 */
		fc_oldcontext = MemoryContextSwitchTo(fc_funcctx->multi_call_memory_ctx);

		/* 为用户上下文分配内存 */
		fc_pindex = (int *) palloc(sizeof(int));
		*fc_pindex = 0;
		fc_funcctx->user_fctx = (void *) fc_pindex;

		/*
		 * 为结果元组构建 tupdesc。这必须与该函数的
		 * pg_proc 条目匹配!
		 */
		fc_tupdesc = CreateTemplateTupleDesc(3);
		TupleDescInitEntry(fc_tupdesc, (AttrNumber) 1, "abbrev",
						   TEXTOID, -1, 0);
		TupleDescInitEntry(fc_tupdesc, (AttrNumber) 2, "utc_offset",
						   INTERVALOID, -1, 0);
		TupleDescInitEntry(fc_tupdesc, (AttrNumber) 3, "is_dst",
						   BOOLOID, -1, 0);

		fc_funcctx->tuple_desc = BlessTupleDesc(fc_tupdesc);
		MemoryContextSwitchTo(fc_oldcontext);
	}

	/* 每次调用函数时完成的工作 */
	fc_funcctx = SRF_PERCALL_SETUP();
	fc_pindex = (int *) fc_funcctx->user_fctx;

	if (zoneabbrevtbl == NULL ||
		*fc_pindex >= zoneabbrevtbl->numabbrevs)
		SRF_RETURN_DONE(fc_funcctx);

	fc_tp = zoneabbrevtbl->abbrevs + *fc_pindex;

	switch (fc_tp->type)
	{
		case TZ:
			fc_gmtoffset = fc_tp->value;
			fc_is_dst = false;
			break;
		case DTZ:
			fc_gmtoffset = fc_tp->value;
			fc_is_dst = true;
			break;
		case DYNTZ:
			{
				/* 确定简称的当前含义 */
				pg_tz	   *fc_tzp;
				TimestampTz fc_now;
				int			fc_isdst;

				fc_tzp = fc_FetchDynamicTimeZone(zoneabbrevtbl, fc_tp);
				fc_now = GetCurrentTransactionStartTimestamp();
				fc_gmtoffset = -DetermineTimeZoneAbbrevOffsetTS(fc_now,
															 fc_tp->token,
															 fc_tzp,
															 &fc_isdst);
				fc_is_dst = (bool) fc_isdst;
				break;
			}
		default:
			elog(ERROR, "unrecognized timezone type %d", (int) fc_tp->type);
			fc_gmtoffset = 0;		/* 保持编译器安静 */
			fc_is_dst = false;
			break;
	}

	MemSet(fc_nulls, 0, sizeof(fc_nulls));

	/*
	 * 将名称转换为文本，使用与 ParseDateTime() 所使用的
	 * 大写转换相反的转换。
	 */
	strlcpy(fc_buffer, fc_tp->token, sizeof(fc_buffer));
	for (fc_p = (unsigned char *) fc_buffer; *fc_p; fc_p++)
		*fc_p = pg_toupper(*fc_p);

	fc_values[0] = CStringGetTextDatum(fc_buffer);

	/* 将偏移量（以秒为单位）转换为时间间隔；不会溢出 */
	MemSet(&fc_itm_in, 0, sizeof(struct pg_itm_in));
	fc_itm_in.tm_usec = (int64) fc_gmtoffset * USECS_PER_SEC;
	fc_resInterval = (Interval *) palloc(sizeof(Interval));
	(void) itmin2interval(&fc_itm_in, fc_resInterval);
	fc_values[1] = IntervalPGetDatum(fc_resInterval);

	fc_values[2] = BoolGetDatum(fc_is_dst);

	(*fc_pindex)++;

	fc_tuple = heap_form_tuple(fc_funcctx->tuple_desc, fc_values, fc_nulls);
	fc_result = HeapTupleGetDatum(fc_tuple);

	SRF_RETURN_NEXT(fc_funcctx, fc_result);
}

/*
 * 这个返回集合的函数读取所有可用的完整时区
 * 并返回一个 (name, abbrev, utc_offset, is_dst) 的集合。
 */
Datum pg_timezone_names(PG_FUNCTION_ARGS)
{
	ReturnSetInfo *fc_rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
	pg_tzenum  *fc_tzenum;
	pg_tz	   *fc_tz;
	Datum		fc_values[4];
	bool		fc_nulls[4];
	int			fc_tzoff;
	struct pg_tm fc_tm;
	fsec_t		fc_fsec;
	const char *fc_tzn;
	Interval   *fc_resInterval;
	struct pg_itm_in fc_itm_in;

	InitMaterializedSRF(fcinfo, 0);

	/* 初始化时区扫描代码 */
	fc_tzenum = pg_tzenumerate_start();

	/* 查找另一个区域以显示 */
	for (;;)
	{
		fc_tz = pg_tzenumerate_next(fc_tzenum);
		if (!fc_tz)
			break;

		/* 将 now() 转换为该时区上的本地时间 */
		if (timestamp2tm(GetCurrentTransactionStartTimestamp(),
						 &fc_tzoff, &fc_tm, &fc_fsec, &fc_tzn, fc_tz) != 0)
			continue;			/* 如果转换失败，则忽略 */

		/*
		 * IANA 那个相当愚蠢的 "Factory" 时区曾经发出荒谬的
		 * 长 "简称"，例如 "必须设置本地时区--请参阅 zic
		 * 手册页" 或 "必须设置本地时区--使用 tzsetup"。虽然
		 * 现代版本的 tzdb 发出更理智的 "-00"，但似乎一些
		 * 愚昧的打包者正在修改 IANA 数据，使其继续
		 * 产生这些字符串。为了防止产生非常宽的
		 * abbrev 列，拒绝荒谬的长简称。
		 */
		if (fc_tzn && strlen(fc_tzn) > 31)
			continue;

		MemSet(fc_nulls, 0, sizeof(fc_nulls));

		fc_values[0] = CStringGetTextDatum(pg_get_timezone_name(fc_tz));
		fc_values[1] = CStringGetTextDatum(fc_tzn ? fc_tzn : "");

		/* 将 tzoff 转换为时间间隔；不会溢出 */
		MemSet(&fc_itm_in, 0, sizeof(struct pg_itm_in));
		fc_itm_in.tm_usec = (int64) -fc_tzoff * USECS_PER_SEC;
		fc_resInterval = (Interval *) palloc(sizeof(Interval));
		(void) itmin2interval(&fc_itm_in, fc_resInterval);
		fc_values[2] = IntervalPGetDatum(fc_resInterval);

		fc_values[3] = BoolGetDatum(fc_tm.tm_isdst > 0);

		tuplestore_putvalues(fc_rsinfo->setResult, fc_rsinfo->setDesc, fc_values, fc_nulls);
	}

	pg_tzenumerate_end(fc_tzenum);
	return (Datum) 0;
}
